Java 继承与多态

1. 继承
继承是父类与子类之间的关系是 is-a 关系,也就是说,继承是子类继承父类的特征和行为,使得子类对象(实例)具有父类的实例域和方法,或子类从父类继承方法,使得子类具有父类相同的行为。通过使用继承,可以快速地创建新的类,提高代码的重用。
下面定义父类为 Person,子类为 Student:
|
|
通过 super(name, age); 可以调用父类构造方法 public Person(String name, int age),其中 super 关键字表示父类(超类),在子类引用父类属性、方法或构造方法时使用。例子中,子类继承了父类的 name 和 age 属性。
-
子类拥有父类对象所有的属性和方法(包括私有属性和私有方法),但是父类中的私有属性和方法子类是无法访问,只是拥有。
-
Java 的继承是单继承,即一个子类只能继承一个父类,但是可以多重继承,如 A 类继承 B 类,B 类继承 C 类。
-
protected允许子类访问父类的字段和方法。若父类方法定义为protected,子类覆写该方法时必须为protected或public。 -
子类的构造方法可以通过
super()调用父类的构造方法。注意,与this()一样需要写在构造方法首行,如果父类没有自定义有参构造方法且子类构造方法首行没有写出,将会默认调用父类的无参构造方法。
2. 对象转型
向上转型:父类引用指向子类对象。
向下转型:指向子类对象的父类引用赋给子类引用(需要强制转换)。
讨论对象转型前先复习下 instanceof 操作符
instanceof 检验某对象是否为某个类的对象,通常用于向下转型的检验,防止出现异常。
student instanceof Person 表示检验 student 是否为 Person 类的对象。 返回类型为 boolean。
要求该对象所属的类和某个类是子类与父类的关系。
例如:
|
|
结果为:
|
|
其中 Person p = new Student(); 表示对象向上转型,意思是 p 是上转型对象,既属于 Person 类又属于 Student 类。
例子:
|
|
向上转型中,只能调用父类自身的方法或者子类里覆写的方法。
向下转型中,主要作用是父类通过实例化子类,可以调用子类独有的方法。
2.1 动态绑定
向上转型的机制可以通过动态绑定来解释。
动态绑定是指在程序运行时,根据具体的对象类型来实现对象和方法绑定(调用)。
编译时类型是由声明该变量时使用的类型决定。
运行时类型由实际赋给该变量的对象决定。
如果编译型类型与运行时类型不一致的话,就会发生运行时动态绑定。对于向上转型:父类引用指向子类对象,这就是一种运行时的动态绑定。
|
|
对于上面一段代码,父类 Person 的 p 指向子类 Student 的对象,所以在编译后 p 本来的 Person 类型,但在程序运行时动态指定的 p 的类型是 Student。
调用时,如果子类 Student 定义了方法 say(),就直接调用,否则将在其超类中寻找方法。但是这样的话每次调用都要进行搜索,时间开销较大,于是 JVM 采用一个包含所有方法签名和实际调用方法的方法表。
动态绑定过程:
-
JVM 提取对象的实际类型的方法表。
-
JVM 搜索方法签名(方法名、参数及其类型)进行匹配。
-
JVM 调用方法。
3. 方法的覆写
在父类和子类中都定义了相同的方法,则子类的方法将覆盖超类的方法。相当于子类对超类方法的修改并复用。
|
|
结果为:
|
|
例子中 Dog 继承 Animal,在子类 Dog 覆写了方法 getInfo,此时在 main 方法使用子类的对象调用该方法将会覆盖父类的相同方法。
在子类非 static 修饰的代码块或方法中可以使用 super 关键字调用父类被覆写的方法。
-
父类和子类都必须具有相同的方法名称、相同的返回类型和相同的参数列表。
-
不能覆写 final 和 static 的方法。
-
访问修饰符的访问范围需要大于等于父类的访问范围。
对比重载:
-
覆写是在子类和父类之间,子类的方法名、返回类型、参数都与父类的方法一致,在继承的情况下才能覆写。重载是同一个类,多个方法的方法名相同但是参数不同(与返回类型,修饰符无关)。
-
方法覆写在运行时执行,而方法重载在编译时执行。(动态绑定与静态绑定)
4. 多态
多态(Java 里面主要指动态多态)是同一个行为具有多个不同表现形式或形态的能力。
实现多态的方式:覆写、接口、抽象类和抽象方法。
多态必要条件(表现形式):继承、覆写、父类引用指向子类对象(向上转型),例如
Person p = new Student();
|
|
结果为:
|
|
以上例子为通过方法的覆写来实现多态。
方法 sound() 在两个不同的类中有不同的实现,在运行时,表达式
dog.sound() 调用 Dog 类的方法,因为 dog 是 Dog 类的对象;
cat.sound() 调用 Cat 类的方法,因为 cat 是 Cat 类的对象。
这里父类 AbstractAnimal 的方法 sound() 本身不需要实现任何功能,仅仅是为了定义方法签名(方法名、参数列表、返回类型),目的是让子类去覆写它,故将父类的方法 sound() 声明为抽象方法。
因为抽象方法本身是无法执行的,故该父类也无法实例化,需要将类声明为 abstract 抽象类才能正常编译。有关抽象类与抽象方法的内容在后续笔记补充。