前言
1. 学习视频:
尚硅谷Java零基础全套视频教程(宋红康2023版,java入门自学必备)_哔哩哔哩_bilibili
2023最新Java学习路线 - 哔哩哔哩
第二阶段:Java面向对象编程
6.面向对象编程(基础)
7.面向对象编程(进阶)
8.面向对象编程(高级)
正文
7. 面向对象编程(进阶)
7.1 this关键字
7.1.1 this调用属性、方法 和 在构造器内使用
7.1.2 this 关键字 调用 构造器
如下,只创建了一个对象,使用this调用其他构造器。但是不能在一个类的两个构造方法中使用this互相调用,不然就形成了一个闭环的循环调用。
注意:只能在构造方法中使用this调用其他构造方法,不能在成员方法中使用。
7.2 面向对象特征之二:继承性(Inheritance)
7.2.1 Java中的继承
1. 继承性的好处:
2. 继承性(Inheritance)概念:
在Java中类的继承是指在一个现有类的基础上去构建一个新类,新类被称为子类,现有类被称为父类,子类会自动拥有父类所有可继承的属性和方法。在程序中,声明一个类继承另一个类,需要使用extends关键字。注意:(对比接口,接口是支持多继承的。)
①Java中,类只支持单继承,不允许多重继承。一个类只能有一个直接父类。
(爸爸类属于子类的直接父类,爷爷类属于孙子子类的间接父类)
②多个类可以继承一个父类。
③多层继承是可以的。即一个类的父类可以再去继承另外的父类。
④但是由于封装性的问题(例如父类中的属性和方法使用private关键字),子类不能直接访问父类中的属性和方法(但是子类是拥有这些属性和方法的。)。假如父类中的a属性使用了private关键字,且a属性提供了public修饰的get和set方法,那么子类就可以通过get和set方法得到a属性的赋值和获取权限。但是不能使用"子类实例.a属性"的方式。
⑤子类继承父类以后,还可以扩展自己特有的功能(体现:增加特有属性、方法)。
3. 默认父类:Object类 (java.lang.Object)
JDK中提供了一个Object类,它是所有类的父类,即每个类都直接或间接继承自该类。
例如我们自定义一个类,它默认就有Object类的方法:toString等。
7.2.2 继承性的权限问题、子类 重写 父类方法的权限问题
1. 继承性的权限问题详解
上面我们已经知道:
2. 子类 重写 父类方法
在继承关系中,子类会自动继承父类中定义的方法,但是有时在子类中需要对继承的方法进行一些修改,即对父类的方法进行重写。注意:
①子类重写的方法要和父类被重写的方法具有相同的方法名、参数列表、返回值类型。
(没有返回值,父、子就都写void。返回值类型也可以是子类(父方法返回值类型用Person,子重写时,可以用Person的子类Student)。)
②子类重写父类方法时,不能使用比父类中被重写的方法更严格的访问权限。(例如父用缺省,子不能用private,子可以用缺省、protected、public。)
③子类中不能重写父类中private权限修饰的方法。
④在子类中重写时加上“@Override 重写注解”,可以校验重写格式正误。
区分:方法的 重载与重写
重载(overload):一个类中定义多个同名方法,但参数类型或个数不同。
重写(override):两个类有继承关系,子类覆盖父类中同名、同参数、同返回值类型的方法。
(没有相关性,只是名字长得像。)(有相关性,名字不像。)
7.3 继承性:super关键字的使用
当子类重写父类方法后,子类对象将无法访问(子类.父类成员变量)父类被重写的方法,为了解决这个问题,在Java中专门提供了一个super关键字用于访问父类的成员。例如访问父类的成员变量、成员方法、构造方法。
7.3.1 super调用属性、方法
super.成员变量
super.成员方法([参数1,参数2...])
eat()方法在子类中重写过之后,用this和省略this调用的都是子类重写后的方法。只有使用super.eat()调用的才是父类的eat()方法。
注意:①成员变量的值遵循就近原则查找。即父类name="柳仙仙",子类定义name="仙子仙"。子类最终name="仙子仙"。(先就近在子类找,找到name="仙子柳",不会再去找父类。子类找不到(子类没定义name成员变量,说明name是继承自父类的),才会找父类。)如果写super.name,则代表父类name,直接查找父类name。
7.3.2 super调用父类的构造器
super([参数1,参数2...])
注意:
①子类继承父类时,不会继承父类的构造器。只能通过super(形参列表)调用父类构造器。
②super调用父类构造方法的代码必须位于子类构造方法的第一行,且只能出现一次。
③在子类的构造方法中一定会调用父类的构造方法,可以通过super指定调用父类的哪个构造方法。如果没有指定,(类默认都有一个无参的构造方法)在子类实例化时,会默认调用父类无参的构造方法super()。如下:
public Dog(){ //子类默认无参的构造器
super(); //父类默认无参的构造器
} //父类也有无参的构造方法时,public Dog(){ }中可以不写super(); ,会默认调用。
但是如果父类中只写了一个有参的构造方法(编译器不会再默认添加一个无参的构造方法),子类只写public Dog(){ },这时编译器认为不调用父类的任何构造器,会报错。所以必须写为:
public Dog(){ //子类默认无参的构造器
super(参数); //调用父类有参的构造器
}
可见在定义一个类时,如果没有特殊要求,尽量在类中定义一个无参的构造方法,避免在继承时出现错误。
7.4 继承性:子类对象实例化的全过程
7.5 面向对象特征之三:多态性(方法)(属性不存在多态性)
7.5.1 多态性
在设计一个方法时,通常希望该方法具备一定的通用性。例如实现动物叫,希望方法在传入不同动物类型名时,实现不同动物的叫声。在同一个方法中,这种由于参数类型不同而导致执行效果各异的现象就是多态。(方法的多态性:方法的重写——同名,不同参数类型或不同参数个数)
在Java中为了实现多态,允许使用一个父类类型的变量来引用一个子类类型的对象,根据被引用子类对象特征的不同,得到不同的运行结果。
子类对象的多态性:父类的引用指向子类的对象。(或 子类的对象 赋给 父类的引用)
比如:Person p2 = new Man();
注意:成员变量并不满足多态性。p2是Person,p2的属性也是Person的类型和值。而不是子类。
7.5.2 多态性的应用案例
①多态性的应用:虚拟方法调用。 (也可称动态绑定:编译时和运行时调用的不是一种方法。)
在多态场景下,调用方法:
编译时,认为方法是左边声明的父类类型的方法(即被重写的方法)。
执行时,实际执行的是子类重写父类的方法。
简称为,编译看左边(父类),运行看右边(子类)。
②多态性的使用前提:①要有类的继承关系;②要有方法的重写。
③多态的适用性:适用于成员方法,不适用于成员变量(属性)。
④多态的好处和弊端:
弊端:①在多态的场景下,我们创建了子类的对象,也加载了子类特有的属性和方法,但是由于声明为父类的引用,导致我们不能直接调用子类特有的这些属性和方法。只能执行子类重写父类的方法。
好处:①极大的减少了代码的冗余,不需要定义多个重载的方法。
我们只需要传参的时候,传入参数信用卡,cust就知道去信用卡取钱。而不必写两个用户取钱的getAccount()方法去区分信用卡和储蓄卡取钱。
理解:
①解决了方法同名问题,使程序更加灵活,从而有效地提高程序的可扩展性和可维护性。
②高内聚:同一方法实现不同功能;低耦合:大量不同子类具体执行不同行为特征。
7.5.3 多态性的逆过程:向下转型,使用关键字instanceof
基于这个弊端:①在多态的场景下,我们创建了子类的对象,也加载了子类特有的属性和方法,但是由于声明为父类的引用,导致我们不能直接调用子类特有的这些属性和方法。只能执行子类重写的父类的方法。
在这个过程中,子类对象赋值给父类变量的时候,在编译期间,做了一次类型转换。因此,想要调用子类特有的方法,必须再做一次类型转换,使得编译通过。(p2、m1指向堆空间中的同一个对象实体(地址值相同)。所以 m1==p2; 是true。)
Person p2 = new Man(); //一次类型转换
Man m1 = (Man)p2; //强制类型转换
向下转型可能出现问题:类型转换异常(ClassCastException)
假如:Person p3 = new Woman(); //一次类型转换
显然我们不能把p3进行强制类型转换成man。他们都是子类,不能互相转换。
可以加一个判断,使用关键字instanceof,判断一个对象是否是某个类(或接口)的实例或者子类实例。格式:
对象(或者对象引用变量) instanceof 类(或接口)
if(p2 instanceof Man){ //是Man类的实例
Man m1 = (Man)p2 ; //强制类型转换
}else{
System.out.println("p2 不是一个Man");
}
7.5.4 练习题
7.6 Object类
7.6.1 Object类介绍与常用方法使用
JDK中提供了一个Object类,它是所有类的父类,即每个类都直接或间接继承自该类。
例如我们自定义一个类,它默认就有Object类的方法:toString等。
- Object类型的变量 与 除Object以外的 任意引用数据类型的对象 都存在多态引用。
- Object类中没有声明属性,只提供了一个空参的构造器。
7.6.2 Object中clone()的使用
所以目前创建对象有两种方式:new和clone()。以后还会见到其他方式。
7.6.3 Object中finalize()的使用(从JDK9开始就过时了!!)
例如关闭浏览器时,临关闭前保存一下你的用户信息,在你再次登录时,就会自动填写上次的用户信息。
之所以过时,是因为可能导致一些问题:finalize()方法可能导致程序内部出现循环引用,导致此对象不能被回收。
7.6. Object中equals()的使用(重写使用IDEA可以自动生成)
任何引用数据类型都可以使用。
①Object中equals()的定义:
②子类使用说明:(重写,比较实体内容;没重写,比较引用地址)
自定义的类在没有重写Object中equals()方法的情况下,调用的就是Object类中声明的equals()方法,即比较两个对象的引用地址是否相同。(或 比较两个对象是否指向了堆空间中的同一个对象实体)
重写了equals()方法:对于像String、File、Date和包装类等,他们都重写了Object类的equals()方法,用于比较两个对象的实体内容是否相同。
③平常比较我们为了比较实体内容,要重写equals()方法。不重写直接用==就可以了。
例如:为了比较两个对象的信息
public boolean equals( Object obj){
if(this == obj){//1.先判断地址值
return true;
}
if(obj instanceof User){//2.判断是否是同类或子类
User user = (User)obj; //Object类先强转成User
//3.比较具体内容
//方式一: name要比较每一个字符
if(this.name.equals(user.name) && this.age == user.age){
return true;
}else{return false;}
//方式二
// return (this.name.equals(user.name) && this.age == user.age);
}else{
return false;
}
}
多态性:引用形式是Object,得强转成子类User的类型,才能调用子类的方法和属性, 否则调用的是父类Object的方法。
④我们还可以让IDEA自动实现:
alt+insert出现:选择生成equals()方法
⑤在开发中的使用:
注意相同字符串使用的地址值也是相同的:
应用:
因为Customer是类,要在Customer类中重写equals()方法,比较。结果是false。是因为Customer类的属性中Account也是一个类,也要重写equals()方法。这样结果才是true。
7.6. Object中toString()的使用(重写:具体内容,不重写:地址值)
类名+@+地址值。和直接打印u1(是去Object中调toString())效果一样。
同样可重写可自动生成:
复习
Java的单继承是因为:子类对象实例化的时候没办法区分从多个父类继承的同名方法或构造器,属性也不好区分。所以单继承,只有一个父类。