面向对象编程——多态
1. 向上转型
父类的引用指向了一个子类的对象
public class Animal {
public void show(){
System.out.println("This is a animal");
}
}
public class Cat extends Animal{
@Override
// 对父类 show() 方法重写
public void show() {
System.out.println("This is a cat");
}
public void fish() {
System.out.println("i like fish");
}
}
public class Main {
public static void main(String[] args) {
Animal animal = new Cat();
animal.show();
}
}
结果:
倘若用 animal 调用 Cat 类中的 fish() 方法,则会报错:
- 从上述例子来看,animal 是父类 Animal 的引用,指向一个子类 Cat 的实例,用 is - a 来解释的话,意思是: “ 猫是一个动物 ”;但我们不能调用子类的 fish()方法,因为如果调用则可以理解为: “ 动物喜欢鱼 ”,这样显然是行不通的。
- 也就是说:当向上转型之后,父类引用变量可以访问父类的属性和方法,但不能访问子类的属性和方法,由于上述例子中子类重写了父类的 show()方法,所以调用的 show()方法是子类的,若子类没有对show()方法进行重写,那么结果将是调用父类的show()方法。
重写内容见下面第 3 条
无重写情况下的结果:
- 若用子类的引用来指向父类的实例,也就相当于向下转型,编译就会出错,还是用 is - a 来套用,意思为: 动物是一只猫, 这个逻辑显然是错误的。
但并不是所有的向下转型都是不行的:只有当这个对象原本就是子类对象通过向上转型得到的,才可以成功转型。
示例:
结果:
如果新建一个 Dog 类的引用变量,引用向下转型的 animal ,则结果是能够编译,但运行会出现错误:
2. 动态绑定
同样拿上述 Animal 和 Cat 来举例,我们定义俩个 Animal 类型的引用,分别指向 Animal 类型的实例和 Cat 类型的实例,然后都调用 show()方法,此时结果显示如下图所示:
由结果可以看出,animal1 调用了父类的 show 方法, animal2 调用了子类的 show 方法。
- 如果 show 方法只在父类中存在,animal2 则会调用父类的 show 方法;
- 如果 show 方法只在子类中存在, animal2 调用则会报错;
- 如果 show 方法在父类和子类中都存在(也就是上述这个例子),此时调用 show 就会涉及到动态绑定;
在调用某个方法时,要看引用究竟指向一个父类的实例还是一个子类的实例(由于在运行时才能确定,而不是编译期,因此称作为“动态绑定”)。
3. 方法重写
针对上述代码例子中的 show 方法来说,子类实现对父类的同名方法,并且参数类型和个数完全相同的情况,称为重写/覆写/覆盖。
注:重写时需要给子类方法加上注解 @Override(这个只是注解之一,可自定义,学会使用即可) ,显式的告诉编译器当前这个子类的方法时重写了父类的方法,可以不写,但加注解可以防止在编写大量方法时我们忘记已经写过此方法,防止无意中写了这个方法的重写代码。
要注意,重写和重载是完全俩个东西,不能混淆:
重写Overriding | 重载Overloading | |
---|---|---|
方法名 | 同名 | 同名 |
参数列表(参数类型、参数个数、参数顺序) | 相同 | 不同 |
返回值类型 | 相同 | 可相同可不同 |
- 方法重写:
测试:
结果:
- 方法重载:
测试:
结果:
- 普通方法可以重写, static 修饰的方法不能够重写;
- 重写子类的方法的访问权限不能低于父类的方法访问权限;
- 冲洗的方法返回值类型不一定和父类的方法相同(但最好相同,除特殊情况外);