多态涉及的几个概念
- 向上转型(upcasting)
- 子类型 –> 父类型
- 又称为:自动类型转换
- 向下转型(downcasting)
- 父类型 –> 子类型
- 又称为:强制类型转换【需要加 强制类型转换符】
- 无论是向上转型还是向下转型,两种类型之间必须要有继承关系,没有继承关系,程序是无法编译通过的。
结合例子分析
1 | Animal a1 = new Cat(); // 其中 Animal 是父类,Cat 是子类 |
根据上面的语句,Cat is Animal
是合理的。 new Cat()
创建的对象类型是Cat
,a1 引用的数据类型是 Animal
,可见它们进行了类型转换,子类型转换为父类型,称为 向上转型/upcasting,或者自动类型转换。
Java 允许:父类型引用指向子类型对象
1 | a1.move(); // '猫在走猫步' |
Java 程序永远分为 编译阶段和运行阶段
先分析编译阶段,再分析运行阶段,编译无法通过,根本是无法运行的
编译阶段编译器检查 a1 这个引用的数据类型为
Animal
,由于Animal.class
字节码当中有move()
方法,所以编译通过了。这个过程,我们称为静态绑定,编译阶段绑定,只有静态绑定成功之后才有后续的运行在程序运行阶段,JVM 堆内存当中真实创建的对象是
Cat
对象,那么以上程序在运行阶段一定会调用Cat
对象的move()
方法,此时发生了程序的动态绑定,运行阶段绑定无论
Cat
类有没有重写move
方法,运行阶段一定调用的是Cat
对象的move
方法,因为底层真实对象就是Cat
对象父类型引用指向子类型对象这种机制导致程序存在编译阶段和运行阶段绑定两种不同的形态/状态,这种机制可以称为一种多态语法机制
1 | a1.catchMouse(); |
以上程序无法调用,因为编译阶段编译器检查到 a1 的类型是 Animal
类型,但没有在 Animal.class
字节码文件中查找到catchMouse()
方法,导致静态绑定失败,也就是说编译失败,更别说运行了
想要调用,可以将 c1 强制类型转换为 Cat
类型,a1 的类型是 Animal
(父类),转换为Cat
(子类),被称为向下转型/downcasting/强制类型转换
注意:向下转型需要两种类型之间必须有继承关系,需要加强制类型转换符
什么时候需要使用向下转型呢?
- 当调用的方法是子类中持有,父类不存在的,必须进行向下转型!
1 | Cat a1 = (Cat)c1; |
1 | Animal a2 = new Bird(); |
以上程序编译是没有问题的,因为编译器检查到 a2 的数据类型是 Animal
,Animal
和 Cat
之间存在继承关系,并且Animal
是父类型,Cat
是子类型,父类型转换成子类型叫做向下转型,语法合格;
虽然编译通过了,但是程序在运行阶段会出现异常,因为 JVM 堆内存当中真实存在的对象是 Bird
类型,Bird
对象无法转换成 Cat
对象,因为两种类型之间不存在任何继承关系,此时出现了著名的异常:java.lang.ClassCastException 类型转换异常,这种异常总是在“向下转型的时候”出现
instanceof 运算符
向上转型只要编译通过,运行一定不会出现问题:
Animal a = new Cat();
向下转型编译通过,运行可能错误:
Animal a2 = new Bird();Cat a2 = (Cat)a2;
怎么避免向下转型的 ClassCastException 呢?
- 使用 instanceof 运算符
instanceof 运算符
- (引用 instanceof 数据类型名) 得到布尔值
- a instanceof Animal
- true 表示:a 这个引用指向的对象是一个 Animal类型
- false 表示:a 这个引用指向的对象不是一个 Animal类型
Java规范中要求:在进行强制类型转换之前,建议采用
instanceof
运算符进行判断,避免 ClassCastException 异常的发生,这是一种编程好习惯!
1 | publice static void main(String[] args) { |
多态的作用
面向对象编程的核心:定义好类,然后将类实例化为对象,给一个环境驱使一下,让各个对象之间协作起来形成一个系统
多态的作用:
- 降低程序耦合性,提高程序扩展性
- 能使用多态,尽量使用
- 父类型引用指向子类型对象
核心:面向抽象编程,尽量不要面向具体编程!