以下内容来自周志明的《深入理解Java虚拟机》。
前一篇说了静态分派和重载有关,现在的动态分派就和覆盖Override有关了。
先看代码:
public class DynamicDispatch { static abstract class Human { protected abstract void sayHello(); } static class Man extends Human { @Override protected void sayHello() { System.out.println("man"); } } static class WoMan extends Human { @Override protected void sayHello() { System.out.println("woman"); } } public static void main(String[] args) { Human man = new Man(); Human woman = new WoMan(); man.sayHello(); woman.sayHello(); man = new WoMan(); man.sayHello(); } }
结果输出:
man
woman
woman
这里不再根据静态类型来决定,因为静态类型同样都是Human的两个变量man和woman在调用sayHello()方法时执行了不同的行为,因为这两个变量的实际类型不同。
运行期根据实际类型其确定方法执行版本的分派过程称为动态分派。
原书中作者使用了javap命令输出了这段代码的字节码来分析为何是运行时的不同,还讲解了invokevirtual指令的解析步骤,这里就不赘述了。
单分派和多分派
public class Dispatch { static class QQ { } static class _360 { } public static class Father { public void hardChoice(QQ args) { System.out.println("father choose qq"); } public void hardChoice(_360 args) { System.out.println("father choose 360"); } } public static class Son extends Father { public void hardChoice(QQ args) { System.out.println("son choose qq"); } public void hardChoice(_360 args) { System.out.println("son choose 360"); } } public static void main(String[] args) { Father father = new Father(); Father son = new Son(); father.hardChoice(new _360()); son.hardChoice(new QQ()); } }
//输出 father choose 360 son choose qq
java语言的静态分派属于多分派类型,在编译阶段,选择目标方法的依据有两点:
1. 静态类型是Father还是Son
2. 方法参数是QQ还是360
动态分派属于单分派类型,在运行时,执行son.hardChoice(new QQ())这句代码时,由于编译器已经决定目标方法的签名必须为harChoice(QQ),虚拟机不会关系传递过来的参数QQ到底是“腾讯QQ”还是"奇瑞QQ",因为这时参数的静态类型和实际类型都对方法的选择不会构成影响,唯一可以影响虚拟机选择的因为只有方法的接受者的类型是Father还是Son。
再重申一下:静态分派在编译器就完成了,动态分派是运行时才进行的。所以Father son=new Son()这句,虽然静态类型是Father,但是运行后的实际类型是Son,所以son.hardChoice执行的是Son类里的方法而不是Father类里的。