1. 多态
面向对象三特特征中,除了封装、继承,还有多态。(为什么出现这种需求,不是很理解)
关于多态中涉及到的几个概念:
向上转型(upcasting):
子类型转换成父类型,也被称为自动类型转换。
向下转型(downcasting):
父类型转换成子类型,也被称为强制类型转换。(需要加强制类型转换)
注意,无论是向上转型还是向下转型,两种类型之间必须有继承关系。没有继承关系,程序是无法编译通过的。
多态即多种状态,在Java程序中,有编译和运行两个阶段。多态指的是编译时是一种状态,运行时是另一种状态。比如向上转型之后(左侧引用是父类型,右侧实际对象是子类型),父类型引用指向子类型对象。即在编译的时候,父类型引用调用自己类的方法;但是在运行的时候,调用的其实是子类中的同名方法。即多种状态。
多态可以降低程序的耦合度,提高程序的可扩展力。能使用多态尽量使用多态,核心是面向抽象编程,尽量不要面向具体编程。多态的底层实质上还是指向的最终的那个具体的类对象。
这样有一个好处,就是在传参的时候,形参可以只定义一种(父类型引用),实参可以传入它的任何子类。类似重载而又不需要定义多个方法。
1.1 流程分析
Animal、Cat、Bird三个类之间的关系:
- Cat继承Animal
- Bird继承Animal
- Cat和Bird之间没有任何关系
1 | Animal a = new Cat(); |
分析:
首先Animal和Cat之间存在继承关系,并且Animal是父类,Cat是子类。Animal类中有move()方法,Cat is a Animal,new Cat() 创建的对象的类型是Cat,a这个引用的数据类型是Animal,可见它们进行了类型转换。子类型转换成父类型,称为向上转型。(注意,这是把Cat对象的内存地址赋值给Animal类型的引用,所以是子类型转换成父类型),是自动类型转换。
- Java程序分为编译阶段和运行阶段,先编译再运行。
- 在编译阶段,编译器检查语法,a的引用数据类型为Animal,父类型指向子类型,满足自动类型转换,并且Animal.class字节码文件中有move方法,所以编译通过了。这个编译阶段绑定的过程称为静态绑定,只有静态绑定成功之后才有后续的运行。
- 在运行阶段,JVM堆内存当中真实创建的对象是Cat对象,那么运行阶段则会调用Cat对象的move()方法,此时发生了程序的动态绑定,在运行阶段绑定。
- 注意,无论Cat有没有重写move()方法,运行阶段一定调用的是Cat对象的move方法,因为底层的真实对象是Cat对象。
- 父类型引用指向子类型对象这种机制导致程序在编译阶段和运行阶段绑定两种不同的状态。
这种机制可以称为一种多态机制,本科时候,似乎可以这么认为,这种向上转型就是:父类型的引用仅仅能访问 子类型对象中 继承父类中的那部分区域。
1 | Animal a = new Cat(); |
分析:
上述的注释行就会报错,因为编译阶段,检测到a是Animal数据类型,但是.class字节码文件中的Animal并没有catchMouse()方法,所以静态绑定失败,即编译未通过。
为了执行catchMouse()方法,必须使得编译通过,即Cat类型才有catchMouse()方法,此时可以向下转型,即子类型引用指向父类型对象。将a的数据类型转换为Cat,即第三行的强制类型转换。
注意,向下转型也需要两种类型之间有继承关系,不然编译报错,强制类型转换需要加强制类型转换符。
和向上转型对比,什么时候需要使用向下转型呢?
前面提到过,向上转型只能访问父类型中有的,那么当调用的方法或者访问的属性是子类型中特有的,就必须进行向下转型。
注意,此时可能会有疑问,向上转型只能访问到父类型中有的。这肯定是不满足要求的,否则继承的意义就不是那么明显了。为了访问子类型特有的,还需要向下转型。转来转去还是回到了原来,这不累吗?
其实不是的,父类型引用指向子类型对象,这部分满足了形参可以只定义一种类型,而实参则可以传多种子类型,类似重载。传进来之后,可根据需求进行向下类型转换,降低耦合度,提高可扩展力。假如有多个子类,如何判断向下转换到哪个子类型呢,参加案例中的instanceof关键字。instanceof是用来判断前者是否是后者的一个实例,(也就是在底层内存上,前者是否指向的是后者)。
2. 简单案例
1 | class Animals{ |
3. 备注
参考B站《动力节点》。