关于 Java 多态(Polymorphism)
之前看 Thinking in Java 的时候自认为理解了 Java 中的多态性和方法动态绑定,直到看到下面这个栗子:
1 | class A { |
最后的输出中,前三条是毫无疑问的,从第四条开始好像就不太符合我们直觉了,a2
的实际类型是B
, a2.show(b)
调用的不应该是B.show(B)
这个方法吗,为什么会调用B.show(A)
方法呢?
这涉及到重载和重写在 JVM 中的实现,是 Java 多态性的基本体现。通过两个例子来分别说明:
重载(Overload) – 重载解析 (overload resolution)
在国内的文档中,这常被称为”静态分派”
1 | // Bicycle 及其子类的源码在上面的栗子中可以找到 |
从运行结果可以看到,不论传入的参数的实际类型是什么,最终调用的都是参数类型为Bicycle
的重载方法。
首先厘清两个概念:静态类型和实际类型
1 | Bicycle bike02 = new MountainBike(); |
其中,Bicycle
也就是我们前面提到的引用变量类型,称为变量的静态类型,或外观类型,MountainBike
称为变量的实际类型。变量本身的静态类型不会被改变,并且最终的静态类型是在编译期可知的;而实际类型变化的结果在运行期才可确定,编译器在编译程序的时候并不知道一个对象的实际类型是什么。
这种依赖静态类型来定位方法执行版本的动作称为静态分派,方法重载就是典型的静态分派,其选择方法的依据有两点:一是静态类型,二是方法参数(参数的类型也是依据静态类型)。
我们再看上面的例子,静态类型是确定的,因此使用哪个重载版本就完全取决于传入的参数类型是数量,而重载是通过参数的静态类型而不是实际类型作为判断依据的,因此在编译阶段,Javac编译器会根据参数的静态类型决定使用哪个重载版本,所以选择了speed(Bicycle)
作为调用目标,并把这个方法的符号引用写到main()
方法里的两条invokevirtual
指令的参数中。
实际上,由于自动转型的存在,在方法重载中,方法的参数类型不一定“完全匹配”,很多情况下编译器只能确定一个“最优”的版本,重载方法是存在匹配优先级的,比如:
1 | private void hi(char c){ |
当我把hi(char c)
注释后,调用的是hi(int)
,把hi(int)
注释后,调用的是hi(Character)
…
自动转型的优先级:char
-> int
-> long
-> float
-> double
-> Character
-> Serializable/Comparable
-> Object
-> 之前类型的 可变长参数类型
没有包含short
,因为那是向下转型,不是自动转型;Serializable/Comparable
这两个是 Character
实现的接口,优先级高于父类。如果同时出现两个参数分别为Serializable
和Comparable<Character>
的重载方法,那它们在此时的优先级是一样的。编译器无法确定要自动转型为哪种类型,会提示类型模糊,拒绝编译。程序必须在调用时显式地指定字面量的静态类型,如:hi((Serializable)'c')
,才能编译通过。
重写(Override) – 动态分派
前面提到的,编译器是不知道实际类型的,因此重写的中方法确定发生在运行期。
运行阶段虚拟机的选择,也就是动态分派的过程。我们再看看文章开始举的例子,在执行a2.show(b)
这句代码时,更准确地说,是在执行这句代码所对应的invokevirtual指令时,由于编译期已经根据静态类型(A
) 和参数类型 (A
) 决定目标方法的签名必须为show(A)
,此时(运行期)方法参数的静态类型、实际类型都对方法的选择不会构成任何影响,唯一可以影响虚拟机选择的因素只有此方法的接受者的实际类型是A
还是B
。a1
的实际类型是B
,因此最终调用的是 B
的 show(A)
方法。
在动态分派中,因为只有一个宗量作为选择依据,所以Java语言的动态分派属于单分派类型,而静态分派,如前所述,由两个宗量进行选择,属于多分派类型。
动态分派的过程优化是通过虚拟方法表(Virtual method table)来进行的。
重载是编译期进行的,也被称为编译器多态/静态绑定/早期绑定;重写属于动态绑定/运行期多态/后期绑定。
不能独立地看待多态,如果没有封装和继承的特性,就无法理解多态,其作为类关系“全景”中的一部分与其他特性协同工作。
References
- Java语言规范 - Method Invocation Expressions
- Oracle Java Tutorials - Polymorphism
- Polymorphism in Java
- 周志明. 深入理解Java虚拟机:JVM高级特性与最佳实践(第2版)