方法调用并不等于方法执行,方法调用阶段唯一的任务就是确定被调用方法的版本。Class文件的编译过程中不包含传统编译中的连接步骤,一切方法调用在Class文件里面存储的都只是符号引用,而不是方法在实际运行内存布局中的入口地址。


    解析


    在类加载的解析阶段,会将Class文件的一部分符号引用转化为直接引用,前提是:方法的程序真正运行之前就有一个可确定的调用版本,并且在运行期间不可变。换句话说,调用目标在程序代码写好、编译器进行编译时就必须确定下来。这类方法的调用称为解析。在Java文件中,私有方法、静态方法、实例构造器、父类方法这四种方法可以在编译期间唯一确定,它们在类加载的时候就会把符号引用解析为该方法的直接引用。这些方法可以称为非虚方法,与之相反,其他方法称为虚方法(除去final方法)。被final修饰的方法,由于不能被覆盖,也不会多态选择,因此也是非虚方法。


    分派


    Java具备面向对象的三个特征:继承、封装和多态。分派调用揭示了多态特性的最基本体现,如“重载”和“重写”在Java虚拟机中如何实现。


    1.静态分派(重载)


    对于 Human man = new Man(); 我们把Human称为变量的静态类型(static Type),或者叫做外观类型(Apparent Type),后面的Man则称为变量的实际类型(Actual Type),静态类型和实际类型在程序中都可以发生变化,区别在于静态类型的变化就仅仅在使用时发生,变量本身的静态类型不会被改变,并且最终的静态类型在编译期可知的;而实际类型变化的结果在运行时才可确定。例如下面代码:



//实际类型变化
Human man = new Man();
man = new Woman();
//静态类型变化
System.out.println((Man) man);
System.out.println((Woman)man);


    虚拟机重载时通过参数的静态类型而不是实际类型作为判定依据。并且静态类型编译期可知,Java编译器会根据参数的静态类型决定使用哪个重载版本。所有依赖静态类型来定位方法执行版本的分派动作称为静态分派,其典型应用是方法 重载。注意重载版本经常不是唯一的,而是最合适的。


    2.动态分派(重写)


    看下面的例子:


Human man = new Man();
Human woman = new Woman();
man.sayhello();  // man
woman.sayhello();  //woman
man = new Woman();
man.sayhello();  //woman


    这里静态变量都是Human的两个变量man和woman在调用sayhello时执行了不同的行为,并且变量man在两次调用中执行了不同的方法。原因很明显:这两个变量的实际类型不同。因为虚拟机执行了多态查找。其步骤为:(1)找到操作数栈顶的第一个元素所指向的对象的实际类型,记作C;(2)如果在类型C中找到与常量中的描述符和简单名称都相符的方法,则进行访问权限校验,如果通过则返回这个方法的直接引用,查找过程结束;如果不通过,则返回异常;(3)否则,按照继承关系从下往上一次对C的各个父类方法进行第二步的搜索和验证;(4)如果没有找到合适的方法,抛出异常。这个过程就是Java语言中重写方法的本质。我们把这种运行期间根据实际类型确定方法执行版本的分派过程称为动态分派。


    简单总结来说:重载是在编译期间就确定的动作,是静态的,从一方面来看也可以说不算是多态的表现。重写是运行时才能确定的,是动态的。


    单分派和多分派


    方法的接收者和方法的参数统称为方法的宗量,基于宗量的多少划分为单分派和多分派。举例说明:


// Son extends Father
Father father = new Father();
Father son = new Son();
father.choice(new A());
son.choice(new B());
//运行结果 
//father use A
//son use B


    在编译期间:也就是静态分派期间,这时选择目标方法依据两点。(1)静态类型是Father还是Son;(2)方法参数是A还是B。最终编译器选择了Father的choice(A)方法和 Father的choice(B)方法。因为是根据两个宗量进行选择,因此Java语言的静态分派(重载)是多分派类型。


    在运行期间:由于编译期已经决定目标方法的签名(choice(A);)因此虚拟机不关心参数的类型,唯一可以影响虚拟机选择的因素只有方法接收者的实际类型是Father还是Son。因为只有一个宗量,所以Java语言的动态分派(重写)是单分派类型。


    总结一句:Java语言是一门静态多分派(重载),动态单分派(重写)的语言。