静态分派
这篇博客参考《深入理解Java虚拟机》。
静态分派在英文技术文档的称呼是“Method Overload Resolution”。
Method Overload Resolution直译过来是方法重载解析。(不知道可不可以把它理解为方法重载时方法版本的调用)。
下面一个程序例子来说明方法重载的选择
package cn.jvm.test1; public class StaticDispatch { static abstract class Human{ } static class Man extends Human{ } static class Woman extends Human{ } public void sayHello(Human guy){ System.out.println("Hello, guy!"); } public void sayHello(Man guy){ System.out.println("Hello,gentleman"); } public void sayHello(Woman guy){ System.out.println("Hello, lady"); } public static void main(String args[]){ Human man = new Man(); Human woman = new Woman(); StaticDispatch sr = new StaticDispatch(); sr.sayHello(man); sr.sayHello(woman); } }
运行结果:
Hello, guy!
Hello, guy!
使用javap命令查看这段代码的字节码,下面截取了main方法的
public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=4, args_size=1 0: new #42 // class cn/jvm/test1/StaticDispat ch$Man 3: dup 4: invokespecial #44 // Method cn/jvm/test1/StaticDispa tch$Man."<init>":()V 7: astore_1 8: new #45 // class cn/jvm/test1/StaticDispat ch$Woman 11: dup 12: invokespecial #47 // Method cn/jvm/test1/StaticDispa tch$Woman."<init>":()V 15: astore_2 16: new #1 // class cn/jvm/test1/StaticDispat ch 19: dup 20: invokespecial #48 // Method "<init>":()V 23: astore_3 24: aload_3 25: aload_1 26: invokevirtual #49 // Method sayHello:(Lcn/jvm/test1/ StaticDispatch$Human;)V 29: aload_3 30: aload_2 31: invokevirtual #49 // Method sayHello:(Lcn/jvm/test1/ StaticDispatch$Human;)V 34: return LineNumberTable: line 26: 0 line 27: 8 line 28: 16 line 29: 24 line 30: 29 line 31: 34 LocalVariableTable: Start Length Slot Name Signature 0 35 0 args [Ljava/lang/String; 8 27 1 man Lcn/jvm/test1/StaticDispatch$Human; 16 19 2 woman Lcn/jvm/test1/StaticDispatch$Human; 24 11 3 sr Lcn/jvm/test1/StaticDispatch; }
上面main方法中红色invokevirtual命令中, Method sayHello:(Lcn/jvm/test1/StaticDispatch$Human;)V,可以发现方法参数为Human, 在方法重载时选择的是sayHello(Human)作为调用目标。
至于为什么会选择执行参数为Human的方法重载呢?
在《深入理解java虚拟机》中,原因讲的很详细,下面纯属搬运工
Human man = new Man()
上面代码中的"Human"称为静态类型(static type)或者叫外观类型(Apparent type), Man称为实际类型(Actual type)。静态类型和实际类型在程序中都可能发生一些变化,区别是静态类型的变化仅仅在使用时发生,而变量本身的静态类型不会被改变,并且最终的静态类型在编译期可知的;而实际类型变化的结果在运行期才可确定,编译器在编译的时候并不知道一个对象的实际类型是什么。例如下面的代码:
//实际类型变化 Human man = new Man(); man = new Woman(); //静态类型变化 sr.sayHello((Man) man); sr.sayHello((Woman) man);
回到第一个程序,在main方法里,已经确定方法的接受者为sr的情况下,方法重载到底调用哪个版本取决去方法的参数的数量和数据类型。程序中刻意的定义了两个静态类型相同但实际;类型不同的变量,但虚拟机在重载时是通过参数的静态类型而不是实际类型作为判定依据。并且静态类型在编译期可知的,因此,在编译阶段,Javac编译器会根据参数的静态类型决定使用哪个重载版本,所以选择了sayHello(Human)作为目标方法。
所有依赖静态类型来定位方法执行版本的分派称为静态分派。静态分派的典型应用是方法重载。静态分派发生在编译阶段,因此确定静态分派的动作实际上不是由虚拟机来执行的。