面向对象——多态与向上、向下转型机制
前言
开头先回忆一下,面向对象的三大特征:封装(数据抽象)、继承、多态。为什么多态排在最后一位,因为它是以前面两个为前提的,尤其是继承。
多态概念梳理
多态本质在于 同一种行为的多样化表达 (这句话不禁让我想起了基因的多样性表达,这是现实世界物种多态的原因)。
对于某一种行为而言,它的多样性体现在两种可能:
1. 行为主体相同,行为受体不同——同样是待客,为什么他对别人温柔对你凶?
2. 行为受体相同,行为主体不同——同样是吃肉,人是煮熟吃,动物生吃。
这是我们在生活中的例子,现在我们迁移到编程中的多态!
我们是不是可以把一个方法称为一种行为,他的调用者我们称为“行为主体” ,他的参数我们称为”行为受体”呢,而把函数体看作是行为的具体内容?
编程中,多态分为 编译时多态 和 运行时多态,也称为 前期绑定 和 动态绑定。
注:存在说法认为多态仅仅限定于运行时的多态,本人认为还是以“同一种行为的多样化表达”的基准,说法是死的,思想是活的,大家也不用在这方面纠结。
编译时多态:编程中的体现是方法重载,调用者是明确的,调用方法有多种,我们根据参数来确定调用哪个方法,从而体现多样性。
运行时多态:根源于继承之后的方法重写,多个子类都继承了相同父类,并且都对父类中某个普通方法进行了重写,从而体现多样性。
实验
首先,先创建继承关系如下:
转型
能实现转型,是运行时多态的必要条件之一。
编译的机制:
根据你引用指定的类型去搜索你的方法是否存在,如果该引用类型中不存在调用方法,报错。
运行时机制:
运行时,是根据引用所指的具体对象类型来调用方法。
当我们将父类中的study()方法注释:
运行时:
从机器级角度分析这个问题:
编译时根据对象引用确定该方法的符号引用名称,运行时根据对象引用指向的对象进行符号引用的重定位。
向上转型
如果你是一个大学生,那么你肯定也是一名学生。
我们对你所属的范围进行了一次周延扩大,这就是一次向上转型。
用处:
1.提高了程序的扩充性,不需要写一些重复的代码。
2.增加代码简洁性,可阅读性。
假设我们有一个老师,他既教高中生,也教大学生,不同学生上课方式不同,正常那我们不是要进行方法重载为每个学生量身定制一个方法吗?但是我们可以让他们自适应:
结果:
向上转型进阶分析:
如果方法调用者和方法参数都是具有继承关系的类型,这个向上转型是怎么转的呢?
观查测试样例:
输出:
A and B
当我们将A类中的show(B)注释掉:
输出:
B and A
规律总结:
先是调用者由下到上向上转型查找对应方法; 若没找到,每次将调用参数向上转型一次,再寻找对应方法。
向下转型(注意点)
你是一个学生,但不一定是大学生。向下转型,一定要注意类型的匹配。
为什么又会有向下转型?向上转型的弊端在于被调用方法受到局限,而当你又想调用子类独有的方法,你就必须用 向下转型 转回去。
所以向下转型一定是先有向上转型作铺垫的。
但是,这边注意一点,你对象是什么类型,你转回去也应该是什么类型。
人->动物->猪 ×
人->动物->人 √
编译没有报错,但运行一定抛出转型异常。
正确做法:
牛刀小试
根据我们之前总结的规律,看看你的答案是否正确吧:
class A {
public String show(D obj) {
return ("A and D");
}
public String show(A obj) {
return ("A and A");
}
}
class B extends A{
public String show(B obj){
return ("B and B");
}
public String show(A obj){
return ("B and A");
}
}
class C extends B{
}
class D extends B{
}
public class Demo {
public static void main(String[] args) {
A a1 = new A();
A a2 = new B();
B b = new B();
C c = new C();
D d = new D();
System.out.println("1--" + a1.show(b));
System.out.println("2--" + a1.show(c));
System.out.println("3--" + a1.show(d));
System.out.println("4--" + a2.show(b));
System.out.println("5--" + a2.show(c));
System.out.println("6--" + a2.show(d));
System.out.println("7--" + b.show(b));
System.out.println("8--" + b.show(c));
System.out.println("9--" + b.show(d));
}
}
输出:
1--A and A
2--A and A
3--A and D
4--B and A
5--B and A
6--A and D
7--B and B
8--B and B
9--A and D