面向对象的特征之二:继承性(inheritance)
定义Person类处理个人信息,定义Student类处理学生信息,Student类继承了Person类的所有属性和方法,并增加了school属性及get和set方法,Person类中的属性和方法在Student都可以使用。
多个子类(派生类,subclass)中存在相同属性和行为时,将这些结构抽取到单独一个父类(超类、基类、superclass)中,那么子类无需再定义这些属性和行为,只要继承父类即可。
1 | //类继承的语法格式: |
继承性的作用:
📌 减少了代码冗余,提高了代码的复用性;
📌 更有利于功能的扩展;
📌 让类与类之间产生了关系,提供了多态的前提。
❗ 注意:不要仅为了获取其他类中某个功能而去继承
继承性的体现:
📌 一旦子类继承父类以后,子类中就获取了父类中声明的所有的属性和方法。
📌 在子类中可以使用父类中定义的方法和属性,也可以创建新的属性和方法,实现功能的拓展。
📌 在java 中继承的关键字用的是“extends”,即子类不是父类的子集,而是对父类的“扩展”。
关于继承的规则:子类不能直接访问父类中私有的(private)的属性和方法。父类中私有的属性和方法,仍然是被子类获取了,只是因为封装性的影响,使得子类不能直接调用父类的私有结构而已。
继承性的规定:
📌 Java只支持类的单继承(接口是多继承)和多层继承,不允许多重继承。一个子类只能有一个父类(java中类的单继承性),一个父类可以派生出多个子类,子类和父类是相对的概念。
📌 子类直接继承的父类称为直接父类,间接继承的父类称为间接父类,子类继承父类以后,就获取了直接父类以及所有间接父类中声明的属性和方法。
📌 如果没有显式的声明一个类的父类的话,则此类继承于java.lang.Object类。所有的java类(除Object类之外)都直接或间接地继承于Object类,意味着所有的java类具有Object类声明的功能。
练习1
在CylinderTest类中创建Cylinder类的对象,设置圆柱的底面半径和高,并输出圆柱的体积。
1 | public class Circle { |
1 | public class Cylinder extends Circle { |
1 | public class CylinderTest{ |
debug常用操作
操作 | 作用 |
---|---|
step into 跳入(f5) | 进入当前行所调用的方法中 |
step over 跳过(f6) | 执行完当前行的语句,进入下一行 |
step return 跳回(f7) | 执行完当前行所在的方法,进入下一行 |
drop to frame | 回到当前行所在方法的第一行 |
resume 恢复 | 执行完当前行所在断点的所有代码,进入下一个断点,如果没有就结束 |
Terminate 终止 | 停止JVM,后面的程序不会再执行 |
方法的重写(override/overwrite)
在子类中可以根据需要对从父类中继承来的方法进行改造,也称为方法的重置、覆盖。在程序执行时,父类的同名方法将被子类的方法所覆盖。重写后实例化子类,通过子类对象调用子父类中的同名同参数的方法时,实际执行的是子类重写父类的方法。
1 | //方法重写的声明: |
重写的要求:
- 子类重写的方法必须和父类被重写的方法具有相同的方法名称和参数列表。
- 子类重写的方法使用的访问权限不能小于父类被重写的方法的访问权限,子类不能重写父类中声明为private权限的方法。
- 子类重写的方法的返回值类型不能大于父类被重写的方法的返回值类型。
- 父类被重写的方法的返回值类型是void,则子类重写的方法的返回值类型只能是void。
- 父类被重写的方法的返回值类型是A类型,则子类重写的方法的返回值类型是A类或它的子类。
- 父类被重写的方法的返回值类型是基本数据类型(比如:double),则子类重写的方法的返回值类型必须是相同的基本数据类型(必须也是double)。
- 子类方法抛出的异常不能大于父类被重写方法的异常。
❗ 注意:子类与父类中同名同参数的方法必须同时声明为非static的(即为重写),或者同时声明为static的(不是重写)。因为static方法是属于类的,子类无法覆盖父类的方法。
1 | //方法重写的举例: |
❗ 重写的重载的区别(面试题)
1.二者的定义细节:重载和重写的概念和具体规则
2.从编译和运行的角度看:
重载:不表现为多态性;重写:表现为多态性。
重载,是指允许存在多个同名方法,而这些方法的参数不同。编译器根据方法不同的参数表,对同名方法的名称做修饰。对于编译器而言,这些同名方法就成了不同的方法,它们的调用地址在编译期就绑定了。Java的重载是可以包括父类和子类的,即子类可以重载父类的同名不同参数的方法。
对重载而言,在方法调用之前,编译器就已经确定了所要调用的方法,这称为早绑定或静态绑定;而对于多态,只等到方法调用的那一刻,解释运行器才会确定所要调用的具体方法,这称为晚绑定或动态绑定。 引用一句Bruce Eckel的话:“不要犯傻,如果它不是晚绑定,它就不是多态。”
权限修饰符
权限修饰符(权限从小到大排列)private,缺省、protected、public置于类的成员定义前,用来限定对象对该类成员的访问权限。
修饰符 | 类内部 | 同一个包 | 不同包的子类 | 同一个工程 |
---|---|---|---|---|
private | yes | |||
缺省 | yes | yes | ||
protected | yes | yes | yes | |
public | yes | yes | yes | yes |
📌 在同一个包中的类中,不可以调用此包中其他类的私有属性及方法;
📌 在不同包的子类中,不能调用其父类中声明为private和缺省权限的属性及方法;
📌 在不同包的普通类中,不可以调用其他包的类声明为private,缺省,protected权限的属性及方法。
super关键字
在java类中使用super来调用父类中的指定操作:
📌 super可用于访问父类中定义的属性
📌 super可用于调用父类中定义的成员方法
📌 super可用于在子类构造器中调用父类的构造器
尤其当子父类出现同名成员时,可以用super表明调用的是父类中的成员。super的追溯不仅限于直接父类。super和this的用法相像,this代表本类对象的引用,super代表父类的内存空间的标识。
super调用属性和方法
- 我们可以在子类的方法或构造器中,通过使用“super.属性”或“super.方法”的方式,显式的调用父类中声明的属性或方法。但是,通常情况下,我们习惯省略“super.”。
- 特殊情况:当子类和父类中定义了同名的属性时,我们要想在子类中调用父类中声明的属性,则必须显式的使用“super.属性”的方式,表明调用的是父类中声明的属性。
- 特殊情况:当子类重写了父类中的方法以后,我们想在子类的方法中调用父类中被重写的方法时,则必须显式的使用“super.方法”的方式,表明调用的是父类中被重写的方法。
1 | //举例 |
super调用父类的构造器
📌 子类中所有的构造器默认都会访问父类中空参数的构造器;
📌 当父类中没有空参数的构造器时,子类的构造器必须通过“this(形参列表)”或“super(形参列表)”显式的调用本类或者父类中相应的构造器。同时,只能“二选一”,且必须放在构造器的首行;
📌 在类的多个构造器中,至少有一个类的构造器使用了“super(形参列表)”,调用父类中的构造器;
📌 如果子类构造器中既未显式调用父类或本类的构造器,且父类中又没有无参的构造器,则编译出错。
1 | //举例 |
1 | public class Student extends Person { |
this和super的区别
区别 | this | super |
---|---|---|
访问属性 | 访问本类中的属性,如果本类没有此属性 则从父类中继续查找 |
直接访问父类中的属性 |
调用方法 | 访问本类中的方法,如果本类没有此方法 则从父类中继续查找 |
直接访问父类中的方法 |
调用构造器 | 调用本类构造器,必须放在构造器的首行 | 调用父类构造器,必须放在子类构造器的首行 |
子类对象的实例化过程
从结果上来看:(继承性)子类继承父类以后,就获取了父类中声明的属性或方法。创建子类的对象,在堆空间中,就会加载所有父类中声明的属性。
从过程上来看:当我们通过子类的构造器创建子类对象时,我们一定会直接或间接的调用其父类的构造器,进而调用父类的父类的构造器,…直到调用了java.lang.Object类中空参的构造器为止。正因为加载过所有的父类的结构,所以才可以看到内存中有父类中的结构,子类对象才可以考虑进行调用。
❗ 虽然创建子类对象时,调用了父类的构造器,但是自始至终就创建过一个对象,即new的子类对象。
面向对象的特征之三:多态性(polymorphism)
多态性,可以理解为一个事物的多种形态。在Java中的体现是,父类的引用指向子类的对象,并且可以直接应用在抽象类和接口上。
Java引用变量有两个类型:编译时类型和运行时类型。编译时类型由声明该变量时使用的类型决定,运行时类型由实际赋给该变量的对象决定。简称:编译时,看左边;运行时,看右边。若编译时类型和运行时类型不一致,就出现了对象的多态性。多态情况下,“看左边”:看的是父类的引用(父类中不具备子类特有的方法);“看右边”:看的是子类的对象(实际运行的是子类重写父类的方法)。
对象的多态——在Java中,子类的对象可以替代父类的对象使用。一个变量只能有一种确定的数据类型,一个引用类型变量可能指向(引用)多种不同类型的对象。
多态性的使用前提:① 类的继承关系 ② 方法的重写
1 | Person p = new Student(); |
子类可看做是特殊的父类,所以父类类型的引用可以指向子类的对象:向上转型(upcasting)。
一个引用类型变量声明为父类的类型,但引用的是子类对象,那么该变量就不能再访问子类中添加的属性和方法。属性是在编译时确定的,编译时e为Person类型,没有成员变量school,因而编译错误。
1 | Student m = new Student(); |
多态性应用举例:方法声明的形参类型为父类类型,可以使用子类的对象作为实参调用该方法。
1 | public class Test { |
虚拟方法调用(Virtual Method Invocation)
正常的方法调用:
1 | Person p = new Person(); |
虚拟方法调用(多态情况下):
子类中定义了与父类同名同参数的方法,在多态情况下,将此时父类的方法称为虚拟方法,父类根据赋给它的不同子类对象,动态调用属于子类的该方法。这样的方法调用在编译期是无法确定的。
1 | //编译时e为Person类型,而方法的调用是在运行时确定的,所以调用的是Student类的getInfo()方法。——动态绑定 |
前提:Person类中定义了welcome()方法,各个子类重写了welcome()。
执行:多态的情况下,调用对象的welcome()方法,实际执行的是子类重写的方法。
多态作用 | 提高了代码的通用性,常称作接口重用 |
---|---|
前提 | 1.需要存在继承或者实现关系 2.有方法的重写 |
成员方法 | 编译时:要查看引用变量所声明的类中是否有所调用的方法。 运行时:实际调用new的对象所属的类中的重写方法。 编译,看左边;运行,看右边。 |
成员变量 | 对象的多态性,只适用于方法,不适用于属性(编译和运行都看左边) |
对象类型转换 (Casting )
有了对象的多态性以后,内存中实际上是加载了子类特有的属性和方法的,但是由于变量声明为父类类型,导致编译时,只能调用父类中声明的属性和方法。子类特有的属性和方法不能调用。如何才能调用子类特有的属性和方法?向下转型:使用强制类型转换符。
基本数据类型的Casting | 对象类型的Casting |
---|---|
自动类型转换:小的数据类型可以自动转换成大的数据类型 如long g=20; double d=12.0f |
向上转型:多态 |
强制类型转换: 可以把大的数据类型强制转换(casting)成小的数据类型 如 float f=(float)12.0; int a=(int)1200L |
向下转型: 对Java对象的强制类型转换称为造型, 从父类到子类的类型转换必须通过造型实现 |
无继承关系的引用类型间的转换是非法的,在造型前可以使用instanceof操作符测试一个对象的类型。
instanceof 操作符
x instanceof A:检验x是否为类A的对象,boolean型。如果是返回true,如果不是返回false。
使用情境:为了避免在向下转型时出现ClassCastException的异常,我们在向下转型之前,先进行instanceof的判断,一旦返回true,就进行向下转型。如果返回false,不进行向下转型。
1 | //如果 a instanceof A返回true,则 a instanceof B也返回true。其中,类B是类A的父类。 |
1 | //问题一:编译时通过,运行时不通过 |
练习
练习一
1.若子类重写了父类方法,就意味着子类里定义的方法彻底覆盖了父类里的同名方法,系统将不可能把父类里的方法转移到子类中:编译看左边,运行看右边。
2.对于实例变量则不存在这样的现象,即使子类里定义了与父类完全相同的实例变量,这个实例变量依然不可能覆盖父类中定义的实例变量:编译运行都看左边。
1 | class Base { |
练习二
1 | public class InstanceTest{ |
练习三
定义三个类,父类GeometricObject代表几何形状,子类Circle代表圆形,MyRectangle代表矩形。定义一个测试类GeometricTest,编写equalsArea方法测试两个对象的面积是否相等(注意方法的参数类型,利用动态绑定技术),编写displayGeometricObject方法显示对象的面积(注意方法的参数类型,利用动态绑定技术)。
1 | public class GeometricTest { |
面试题
1.谈谈你对多态性的理解?
① 实现代码的通用性。
② Object类中定义的public boolean equals(Object obj){ }
JDBC:使用java程序操作(获取数据库连接、CRUD)数据库(MySQL、Oracle、DB2、SQL Server)
③ 抽象类、接口的使用肯定体现了多态性。(抽象类、接口不能实例化)
2.多态是编译时行为还是运行时行为?如何证明? 多态是运行时行为
1 | //证明如下: |
1 | //考查多态的笔试题目: |