Java具备面向对象的三大特征:封装、继承、多态。分派过程将会揭示多态特征的一些最基本体现
一、什么是静态分派
所有依赖静态类型来定位方法执行版本的分派动作称为静态分派。静态分派的典型应用是方法重载(Overload)。
二、方法静态分派
//静态分派演示
public class StaticDispatch {
static abstract class Human{}
static class Man extends Human{}
static class Woman extends Human{}
public void say(Human human){
System.out.println("hello,guy");
}
public void say(Man man){
System.out.println("hello,man");
}
public void say(Woman woman){
System.out.println("hello,woman");
}
public static void main(String[] args) {
Human man = new Man();
Human woman = new Woman();
StaticDispatch staticDispatch = new StaticDispatch();
staticDispatch.say(man);
staticDispatch.say(woman);
}
}

相信大家对这个运行结果都不会抱有争议,但是为什么会选择执行参数类型为Human的重载呢?我们先按如下代码定义两个重要概念。
Human man = new Man();
我们把上面代码中的“Human”称为变量的静态类型(Statica Type),或者叫外观类型(Apparent Type),后面的“Man”则称为变量的实际类型(Actual Type),静态类型和实际类型在程序中都可以发生一些变化,区别是静态类型的变化仅仅在使用时发生,变量本身的静态类型不会改变,并且最终的静态类型是在编译期可知的;而实际类型变化的结果在运行期才可确定,编译器在编译程序的时候并不知道一个对象的实际类型是什么。
可以简单的理解为,赋值语句左侧(被赋值的目标类型)的是静态类型,赋值语句右侧(被实例化对象本身的类型)的是实际类型。
//实际类型变化
Human man = new Man();
Human woman = new Woman();
StaticDispatch staticDispatch = new StaticDispatch();
staticDispatch.say(man);
staticDispatch.say(woman);
//静态类型变化
Human man = new Man();
Human woman = new Woman();
StaticDispatch staticDispatch = new StaticDispatch();
staticDispatch.say((Man) man);
staticDispatch.say((Woman) woman);
}

关于静态类型变化的运行结果也是毋庸置疑,两个变量的静态类型一开始都是“Human”,之后在参数中发生了变化,得到了对应的结果。
由此我们可知,虚拟机(编译器)在重载时是通过参数的静态类型,而不是实际类型作为判断依据的。并且静态类型是在编译期可知的,因此在编译阶段,javac编译器会根据参数的静态类决定使用哪个版本的重载,并把这个方法的符号引用写到main()方法里的两条invokevi指令的参数中。
三、重载方法匹配优先级
编译器虽然能确定出方法的重载版本,但很多情况下重载版本并不是“唯一的”,往往只能确定一个“更合适的”版本。产生这种模糊结论的原因是字面量不需要定义,所以字面量没有显式的静态类型,它的静态类型只能通过语言上的规则去理解和推断。
//重载方法匹配优先级
public class Overload {
public static void say(Object arg){
System.out.println("hello Object");
}
public static void say(int arg){
System.out.println("hello int");
}
public static void say(long arg){
System.out.println("hello long");
}
public static void say(char arg){
System.out.println("hello char");
}
public static void say(Character arg){
System.out.println("hello character");
}
public static void say(char... arg){
System.out.println("hello char...");
}
public static void say(Serializable arg){
System.out.println("hello Serializable");
}
public static void main(String[] args) {
say('a');
}
}

这个结果很好理解,但如果注释掉参数为char的方法,结果将会变成:

这时,参数 ‘a’ 发生了一次自动类型转换,’a’除了代表一个字符,还可以代表数字97(对应ASCII码的十进制值),此时int参数的重载也是合适的。接着,我们注释掉参数为int的方法:

这时,发生了两次转型,’a’转型为97后,进一步转为长整型。依次类推,自动转型还可按照
char->int->long->float->double
的顺序转型多次,但不会匹配到byte和short,因为这样的转型是不安全的。继续注释掉参数为long的方法:

这时发生了一次自动装箱,’a’被包装为塔的封装类型java.lang.Character。 继续注释掉参数为character的方法:

这里的输出结果看似有些奇怪,但是进入到java.lang.Character 中,我们可以看到该类实现了Serializable接口。所以在自动装箱之后发现还是找不到对应的方法,就去寻找了它所实现的接口类型。但是Character是绝对不会转型为Integer的,它只能安全的转型为它实现的接口或父类。

这里我们如果再添加一段代码
public static void say(Comparable<Character> arg){
System.out.println("hello Serializable");
}
运行时会因为 Serializable和Comparable<Character>的优先级是一样的而抛出异常
Error:(38, 9) java: 对say的引用不明确
Overload 中的方法 say(java.io.Serializable) 和 Overload 中的方法 say(java.lang.Comparable) 都匹配
下面,我们注释掉以上两个方法再来运行:

很明显,char在装箱之后找不到对应的方法而转型成了父类Object,如果有多个父类,将会依次向上转型。越接近上层,优先级越低。
最后,注释掉参数为Object的方法。此时,只剩下了一个方法,可见变长参数的重载优先级是最低的

注意:
1.有一些在单参数中能成立的自动转型,在变长参数中是不成立的,如char转型为int
2.解析和分派不二选一的排他关系,而是不同层次上筛选、确定目标方法的过程