前面内容请见:
JAVA基础–JAVA SE(知识点总结 Ⅰ )
JAVA基础–JAVA SE(知识点总结 Ⅱ )
JAVA基础–JAVA SE(知识点总结 Ⅲ )
JAVA基础–JAVA SE(知识点总结 Ⅳ )
以下是我们今天的内容:
面向对象
1.继承
将多个类的共性内容抽取出来,放在一个独立的类中,让这个独立的类和其他类产生一种关系
- “继承” ----> 关键字 extends
格式
class 父类名{ 共性内容:姓名,年龄..... 提供公共的访问方法setXXX()/getXXX() } class 子类名 extends 父类名{ }
优点
可以提高代码复用性
可以提高代码的维护性,后期便于维护,针对子类和父类进行维护(子父关系明确)
类与类产生的继承关系,是"多态"的前提条件
在Java中有一个开发原则 "低耦合,高内聚"(Java设计模式都需要遵循这一个原则) * 耦合性:开发中是永远避免不了,可以降低(耦合:类和类的关系) * 内聚性:指的是某个类完成某个功能的一种能力; 尽量不要产生继承关系来完成一个功能,尽量一个类能完成一个类完成; * 低耦合:降低耦合性,减少类和类的关系;
特点
在Java语言中,类和的类的关系是一种继承关系,这个继承只能支持"单继承",不支持多继承
class 父{} class 父2{} class 子 extends 父,父2{} //多继承:不支持 错 class 子 extends 父{} //正常的语法格式 对
类和类关系,虽然不支持多继承,但是层层单继承----> 多层继承
//定义父类 class GrandFather{ public void method(){ System.out.println("我是爷爷"); } } //定义父类 class Father extends GrandFather{ public void show(){ System.out.println("我是父亲"); } } //父类 //class Mother{} //子类 //class Son extends Father,Mother{} //子类不能继承多个父类,多继承语法不支持 class Son extends Father{ //自己的特有功能 public void playGame(){ System.out.println("会玩游戏"); } } public class ExtendsDemo { public static void main(String[] args) { //创建Son类对象 Son s = new Son() ; s.show(); //访问父亲的方法 s.method();//访问爷爷的方法(method方法间接继承过来) s.playGame(); //访问自己的方法 }
注意:
子类继承父类,对于非私有的成员,直接可以继承过来,但是如果是私有成员,它可以通过公共的访问去访问,但不是直接访问的
被私有修饰的东西(成员变量/成员方法),只能在当前类访问的
在继承关系中,构造方法的访问问题(重点):
子类继承父类,子类的所有构造方法都默认访问父类的无参构造方法
为什么?
子类继承父类,会用到父类的数据,需要让父类先进行初始化; 一个类初始化的---肯定需要执行构造方法的
如果一个父类存在有参构造方法,没有无参构造方法,子类的所有构造会出现什么问题?出现了问题,怎么解决?
子类的所有全部构造方法报错,子类继承父类,子类的所有构造方法都默认访问父类的无参构造方法
解决方案1:
手动给出无参构造方法
public 类名(){ }
解决方案2:
假设,人家现在就不需要让你给出父类的无参构造方法,就需要让子类的构造方法,显示的访问父类的有参构造方法 * 要使用关键字 <strong>super</strong>:代表父类空间标识(代表的父类对象的地址值引用) * super() :访问父类的无参构造方法 * super(xxx) :访问父类的有参构造方法 * 这些super一定是在子类构造方法中的第一句话
解决方案3:
保证子类的所有的构造方法某一个构造方法,让父类初始化完毕即可;
先通过子类的无参构造方法里面---this(xxx):访问本类(子类)有参构造方法
再子类的有参构造方法的第一话:让父类初始化 super(xxx):间接访问父类的有参构造方法
子类继承父类,一定要先执行父类的构造方法,初始化完毕之后;然后才能执行子类的构造方法---分层初始化
注意: 子类在隐藏了一个super(),所以先要访问父类构造方法,在执行子类,如果有super(xxx),则super()不自动创建
- 子类继承父类,成员变量的访问问题:
- 情况1: 子类和父类的中成员变量名称不一致,访问时,只要分别访问即可
- 情况2: 子类和父类的成员变量名称一致,访问时:
- 先在子类的局部位置找,有没有这个变量,如果有就使用
- 要是没有,在子类成员位置中继续找,有没有这个变量,如果有就使用
- 要是子类的成员位置也没有,那么会在父类的成员位置继续找,看有没有这个变量,有的话就使用
- 如果父类的成员位置同样还没有的话,则会报错(前提:这个父类没有它的父类了),说明整个子父类中都没有变量
- 遵循"就近原则"
如果是向上转型的时候,父类名 对象名 = new 子类名; ---- > 成员变量输出为父类的值
//父类 class Person{ int age = 20; } //子类 class Man extends Person{ int age = 40; } public class Test{ public static void main(String[] args){ Person person = new Man(); System.out.println(person.age); //20 } }
想要输出子类成员变量值需要用到向下转型(具体后面会分析)
子类继承父类,关于成员方法的访问:
情况1:子类和父类的成员方法名称不一致,调用时,只要分别调用就可以
情况2:子类和父类的成员方法一模一样时(权限修饰符,返回值类型,参数列表都一样)
- 子类将父类的方法覆盖了---->方法重写 :Override---->子类在父类的基础上,将父类的覆盖了,使用自己的功能;
public class Person { //确定属性 private String name; private int age; private String professional; //无参 public Person() { } //有参 public Person(String name, int age, String professional) { this.name = name; this.age = age; this.professional = professional; } ... ... //定义方法 public void eat(){ System.out.println("都喜欢吃"); } public void show(){ System.out.println("姓名:" + name + "\n" + "年龄:" + age + "\n" + "职业:" + professional); } } //子类 public class SourthPerson extends Person{ public SourthPerson() { } ... ... @Override //重写 public void eat() { System.out.println("南方人喜欢吃大白米饭"); } } //测试类 public class PersonTest { public static void main(String[] args) { //无参--南方 SourthPerson sourthPerson = new SourthPerson(); sourthPerson.setName("张三"); sourthPerson.setAge(33); sourthPerson.setProfessional("商人"); sourthPerson.show(); sourthPerson.eat(); sourthPerson.favorite(); } }
补充:
类加载的时候,内存是很快的(继承关系)
父类和子类与静态相关的先执行
静态的东西优先于对象存在 (类名 对象名 = new 类名() 😉
静态代码块>构造代码块>构造方法…
继承关系:分层初始化---->先父类初始化----然后才是子类初始化
class Fu2{ static{ System.out.println("Fu2的静态代码块");//1) } public Fu2(){ System.out.println("Fu2的无参构造方法");//4) } { System.out.println("Fu2的构造代码块"); //3) } } class Zi2 extends Fu2{ public Zi2(){ System.out.println("Zi2的无参构造方法");//6) } { System.out.println("Zi2的构造代码块"); //5) } static{ System.out.println("Zi2的静态代码块"); //2) } } //看程序,写结果 public class Test { public static void main(String[] args) { //创建子类对象 Zi2 z = new Zi2() ; } }
2.final关键字
- final:最终的,不可被更改的,状态修饰符,被final修饰的成员方法,不能被重写,保证父类方法的安全性
class Father{
// public void function(){
public final void function(){
System.out.println("这个文件不能被修改");
}
}
//子类继承父类
class Son extends Father{
public void function(){
System.out.println("这是废弃的文件");
//这里会报错
//'function()' cannot override 'function()' in 'com.hwq.Father'; overridden method is final
//被重写的方法是最终的,但是父类有了final导致不能被覆盖
}
//测试类
public class FinalDemo {
public static void main(String[] args) {
//创建子类对象
Son son = new Son() ;
son.function();
}
}
- 特点: 最终的,无法被修改
- final可以修饰类,该类不能被继承
- final修饰成员方法,此时这个方法不能被子类重写,目的是为了保证方法中某些数据的安全性
- final可以修饰变量,此时这个变量是一个"常量",常驻内存;
- 自定义常量:在开发中,定义一个int类型的
- public static final 数据类型 xxx = 值;// 自定义常量: 编译时期常量,不需要加载;jvm检查语法即可3.
- 自定义常量:在开发中,定义一个int类型的
3.多态
多态
宏观角度(现实生活中): 一个事物在不同时刻体现的不同形态
微观角度(内存中变化): 具体对象在内存中的变化(对象在不同时刻的类型)
多态的前提条件
- 必须有继承关系(类与类)
- 必须存在方法重写,子类部分功能要将父类的功能进行覆盖,重写,子类使用自己的功能体现
- 必须存在父类引用指向类对象:
- 固定格式: 父类名 对象名 = new 子类名() ; //向上转型:使用的父类的东西
多态的成员访问特点: 父类名 对象名 = new 子类名() ;
成员变量: * 编译看左,运行看左 ;
成员方法 (非静态): * 编译看左,运行看后(子类重写了父类的功能)
静态方法:
即使子类出现了和父类一模一样静态方法,不算重写,因为静态方法都是自己类相关的,类成员
编译看左,运行看左;
class Animal{ //父类的静态方法 public static void show(){ System.out.println("show Animal"); } } class Cat extends Animal{ public static void show(){ System.out.println("show Cat"); } } public class DuoTaiDemo { public static void main(String[] args) { Animal.show(); Cat.show(); } }
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-J4poYC6X-1660382709362)(…/Typora/images/image-20220813155957111.png)]
构造方法:
多态的前提条件,有继承关系,跟继承一样,分层初始化
先执行父类的构造方法,然后再是子类的构造方法
class Animal{ int age = 40 ; public Animal(){ System.out.println("Animal的无参构造方法"); } public void eat(){ System.out.println("动物饿了要吃饭"); } public void sleep(){ System.out.println("动物困了要休息"); } //父类的静态方法 public static void show(){ System.out.println("show Animal"); } } //猫类 class Cat extends Animal{ int age = 25 ; public Cat(){ System.out.println("Cat的无参构造方法"); } //将eat和sleep()具体体现,应该重写父类的方法 public static void show(){ System.out.println("show Cat"); } @Override public void eat() { System.out.println("猫吃鱼"); } @Override public void sleep() { System.out.println("猫躺着睡"); } } //狗类 class Dog extends Animal{ @Override public void eat() { System.out.println("狗吃骨头"); } @Override public void sleep() { System.out.println("狗趴着睡"); } } //测试类 public class DuoTaiDemo { public static void main(String[] args) { Animal a = new Cat() ; //堆内存存储的猫 猫是动物 System.out.println(a.age) ;// 编译看左,运行看左 ; a.eat(); //编译看左,运行看右 (前提存在重写) a.sleep();编译看左,运行看右 (前提存在重写) } }
多态的弊端:
- 无法调用子类的特有功能
- 解决方法
- 方法1:创建自己的子类对象
- 子类名 对象名 = new 子类名() ;
- 缺点:
- 本身Fu fu = new Zi() ;已经开辟堆内存空间了,Zi zi = new Zi();又要在堆内存开辟空间,消耗内存空间比较大 ;
- 方法2: 向下转型
- 前提必须向上转型Fu fu = new Zi() ;
- Zi z = (Zi)fu ; //强转的语法
- 优点:节省内存空间
- 方法1:创建自己的子类对象
//父类 class Person{ int age = 20; } //子类 class Man extends Person{ int age = 40; } public class Test{ public static void main(String[] args){ Person person = new Man(); //向上转型 System.out.println(person.age); //20 Man man = (Man) person; //向下转型 System.out.println(man.age); //40 } }
注意事项:
针对多态的向下转型,前提必须有向上转型(父类引用指向子类对象)
但是,写代码的时候,应当注意内存中的变化,否则会出现一种 “异常”
运行时期异常 ---- >java.lang.ClassCastException:类转换异常
一般都出现多态里面向下转型使用不当就会造成这个异常出现
//向上转型 Animal animal = new Cat() ; //堆内存是猫 //向下转型 Dog dog = (Dog) animal; //格式正确:符号强转语法 但是运行会报错: //将猫转成狗,所以要注意内存中的变化,避免类转换异常
- 好处
提高代码的复用性,由继承保证的
提高代码的扩展性,由多态保证 : 父类引用指向子类对象
4.抽象类
- 抽象类: 在一个类中,如果有抽象方法,这个类必须为抽象类(前面要加abstract关键字)
- 抽象类的特点:
不能实例化 (不能创建对象)
必须强制子类完成事情:必须将抽象方法重写
- 抽象方法: 某个事物的一些行为,不给出具体体现,在Java编程中,应该加入一个关键字abstract,这些行为 ---- > “抽象方法”
注意:
- 有抽象方法的类一定是抽象类,否则编译就会报错
- 抽象类中不一定都是抽象方法,也可以用非抽象方法
- 抽象类中可以没有抽象方法
抽象的方法的格式:
根据写方法的格式一样,加入一个关键字abstract,而且没有方法体{} * public abstract 返回值类型 方法名(空参/带参) ;
抽象类的子类:
- 通过具体类才能创建对象
- 抽象的父类名 对象名 = new 具体的子类名() ; //抽象类多态
- 通过具体类才能创建对象
抽象类的成员特点:
成员变量 * 抽象类的成员变量既可以有变量,也可以是自定义常量被final修饰
成员方法 * 抽象类中既可以有抽象方法,也可也有非抽象方法
构造方法 * 既可以定义无参/有参构造方法...
存在抽象类多态,有继承关系,初始化的时候 * 构造方法----分层初始化 ---- > 先父类初始化 ---- > 再子类初始化
补充:
子类继承父类,子类重写父类的抽象方法时
- 必须保证访问权限足够大,要么加public要么跟父类的方法保持一致,否则会报错
如果有一个类没有任何的抽象方法,还要将这个类定义为抽象类的意义?
- 意义就是不能让它new,它如何实例化呢?肯定有具体的子类,进行抽象类多态来操作.
注意事项:
abstract关键字应用范围:
- 定义在类上—抽象类
- abstract 返回值类型 方法/名(空参带参…)
- 定义在方法上----抽象方法
- public abstract 返回值类型 方法名(空参/带参…)
和关键字abstract冲突的关键字
- private关键字冲突
- 因为被private私有的成员方法只能在本类访问,而abstract修饰的成员方法必须强制子类重写,已经超出来的当前类的范围
- final关键字冲突
- 被final修的成员方法,不能被重写,而抽象方法强制子类必须重写
- static关键字冲突
- abstract修饰的方法必须被子类重写,而static修饰的方法,算不上抽象,直接跟类相关的
5.接口
接口: 接口体现的是事物的一种额外功能,需要事物要对接口的功能要进行实现,才具备
Java编码中,体现这些事物本身所不具备的功能,要经过一些特殊的实现才能具备功能-----称为 “接口” ---- > 关键字 interface
格式的写法
interface 接口名{ //命名规范和类名命名一样,见名知意 "大驼峰命名法" 只能为抽象方法 }
注意:
接口比抽象类还抽象 ---- > 特点:不能实例化
接口通过它的子实现类进行实例化(前提,这个类实现 implements 接口)
class 子实现类名 implements 接口名{//实现 }
- 实际开发中:接口的子实现类名的命名—> 接口名的后面+Impl:表示是接口的实现类
- 设计理念:
- 体现的是一种 "like a"的关系
- 继承的设计理念体现的是一种"is a"的关系 :什么是什么的一种
//接口
interface Fly {
void fly(); //需要注意,这里有默认修饰符 public abstract ,要注意后面的权限问题
}
//滑翔松鼠
class Squirrel implements Fly{
@Override
public void fly() {
System.out.println("松鼠学会了短距离滑翔");
}
}
//测试类
public class InterfaceDemo {
public static void main(String[] args) {
//接口不能实例化,需要通过子实现类实例化(必须为具体类)
//测试:接口多态---接口名 对象名 = new 子实现类名();
Fly Squirrel = new Squirrel() ;
Squirrel.fly();
}
}
- 接口的成员特点:
成员变量 * 只能是常量,存在默认修饰符 :public static final
成员方法 * 只能是抽象方法,存在默认修饰符 public abstract
构造方法 * 没有构造方法的---通过子实现类的构造方法来实例化
接口本身意义 * 对外暴露这些功能,让子实现类实现
补充:
关于面向对象中牵扯关系问题: (Java中最基本的单元是类)
类和类:继承关系 extends
Java语言中只支持单继承,不支持多继承,但是可以多层继承
类和接口: 实现关系 implements
一个类继承另一个类的同时,可以实现多个接口
class InterImpl extends SuperIner implements Inter,Inter2{//一个类继承另一个类的同时,可以实现多个接口 @Override public void show() { System.out.println("sInterImpl"); }
接口和接口:继承关系:extends
不仅支持单继承,也可以多继承
interface Inter extends StudyMath,StudyEnglish{ //接口与接口 ---- > 继承关系 :单继承 / 可以多继承 void show() ; }