参考文献:https://blog.csdn.net/u011116672/article/details/50086611
这里所谓的分派指的是在Java中对方法的调用。Java中有三大特性:封装、继承和多态。分派是多态性的体现,Java虚拟机底层提供了我们开发中“重写”和“重载”的底层实现。其中重载属于静态分派,而重写则是动态分派的过程。除了使用分派的方式对方法进行调用之外,还可以使用解析调用,解析调用是在编译期间就已经确定了,在类装载的解析阶段就会把符号引用转化为直接引用,不会延迟到运行期间再去完成。而分派调用则既可以是静态的也可以是动态(就是这里的静态分派和动态分派)的。
1.静态分派
静态分派只会涉及重载,而重载是在编译期间确定的,那么静态分派自然是一个静态的过程(因为还没有涉及到Java虚拟机)。静态分派的最直接的解释是在重载的时候是通过参数的静态类型而不是实际类型作为判断依据的。比如创建一个类O,在O中创建了静态类内部类A,O中又有两个静态类内部类B、C继承了这个静态内部类A,那么实际上当编写如下的代码:
public class O{
static class A{}
static class B extends A{}
static class C extends A{}
public void a(A a){
System.out.println("A method");
}
public void a(B b){
System.out.println("B method");
}
public void a(C c){
System.out.println("C method");
}
public static void main(String[] args){
O o = new O();
A b = new B();
A c = new C();
o.a(b);
o.a(c);
}
}
运行的结果是打印出连个“A method”。原因在于静态类型的变化仅仅在使用时发生,变量本省的类型不会发生变化。比如我们这里中A b = new B();虽然在创建的时候是B的对象,但是当调用o.a(b)的时候才发现是A的对象,所以会输出“A method”。也就是说在发生重载的时候,Java虚拟机是通过参数的静态类型而不是实际参数类型作为判断依据的。因此,在编译阶段,Javac编译器选择了a(A a)这个重载方法。
虽然编译器能够在编译阶段确定方法的版本,但是很多情况下重载的版本不是唯一的,在这种模糊的情况下,编译器会选择一个更合适的版本。
2.动态分派
动态分派的一个最直接的例子是重写。对于重写,我们已经很熟悉了,那么Java虚拟机是如何在程序运行期间确定方法的执行版本的呢?
解释这个现象,就不得不涉及Java虚拟机的invokevirtual指令了,这个指令的解析过程有助于我们更深刻理解重写的本质。该指令的具体解析过程如下:
- 找到操作数栈栈顶的第一个元素所指向的对象的实际类型,记为C
- 如果在类型C中找到与常量中描述符和简单名称都相符的方法,则进行访问权限的校验,如果通过则返回这个方法的直接引用,查找结束;如果不通过,则返回非法访问异常
- 如果在类型C中没有找到,则按照继承关系从下到上依次对C的各个父类进行第2步的搜索和验证过程
-
如果始终没有找到合适的方法,则抛出抽象方法错误的异常
public class Overload {
private static void sayHello(char arg){
System.out.println("hello char");
}
private static void sayHello(Object arg){
System.out.println("hello Object");
}
private static void sayHello(int arg){
System.out.println("hello int");
}
private static void sayHello(long arg){
System.out.println("hello long");
}
// 测试代码
public static void main(String[] args) {
sayHello('a');
}
}
输出 hello char
因为‘a’是一个char类型数据(即静态类型是char),所以会选择参数类型为char的重载方法。
若注释掉sayHello(char arg)方法,那么会输出 hello int
因为‘a’除了可代表字符串,还可代表数字97。因此当没有最合适的sayHello(char arg)方式进行重载时,会选择第二合适(第二优先级)的方法重载,即sayHello(int arg)
当没有最合适的方法进行重载时,会选优先级第二高的的方法进行重载,如此类推。
优先级顺序为:char>int>long>float>double>Character>Serializable>Object>…
其中…为变长参数,将其视为一个数组元素。变长参数的重载优先级最低。
因为 char 转型到 byte 或 short 的过程是不安全的,所以不会选择参数类型为byte 或 short的方法进行重载,故优先级列表里也没有。