Java方法重载的本质
背景
考大家一道题目,下面的类执行结果是什么???
public class DispatcherClient { public static void main(String[] args) { Animal a = new Animal(); Animal a1 = new Dog(); Animal a2 = new Cat(); Execute exe = new Execute(); exe.execute(a); exe.execute(a1); exe.execute(a2); } } class Animal { } class Dog extends Animal { } class Cat extends Animal { } class Execute { public void execute(Animal a) { System.out.println("Animal"); } public void execute(Dog d) { System.out.println("dog"); } public void execute(Cat c) { System.out.println("cat"); } }
不知道大家心里的答案是什么?反正我的答案是错的。
正确的答案是:
为什么是Animal Animal Animal? 而不是Animal dog cat。
类重载本质——静态分派
execute方法是一个重载方法,本质上就是虚拟机JVM如何确定调用哪个方法执行。在java编译后的class文件中存储的只是方法的符号引用,而不是方法在实际运行过程中内存布局的入口地址(直接引用)。而这个方法从符号引用变成直接引用有两种方式,解析和分派。
解析是发生在类加载的解析阶段就会将一部分方法的符号引用转换为直接引用,比如类的静态方法、私有方法、构造方法、父类方法以及final的方法。我们这里不展开阐述,和本例无关。
而我们方法重载的情况下,java采用的是静态分派的方式确定调用方法。
变量类型
在了解静态分派前我们需要了解下变量的类型。
Animal a1 = new Dog();
- 静态类型, 也叫做"外观类型", 比如代码中的"Animal", 它的类型是在编译期就知道。
- 实际类型,也叫"运行时类型", 比如代码中的"Dog", 它是在类运行时才会确定,编译期是不知道的。
Execute exe = new Execute();
exe.execute(a);
exe.execute(a1);
exe.execute(a2);
这里多次调用了execute方法,在方法接收者已经确定是对象exe的前提下,使用哪个重载的方法,就完全取决于传入参数的数量和数据类型。虚拟机在重载时是通过参数的静态类型而不是实际类型作为判断依据的。因为静态类型是编译期可知的,所以,在编译阶段,编译器会根据静态类型决定使用哪个重载版本,如下图例子中的字节码,技术在编译的字节码中确定了它调用的重载方法。
总结
总结下,所有依赖静态类型来定位方法执行版本的分派叫做静态分派。静态分派的典型应用就是方法重载,它是在编译阶段确定的,它会选择一个最合适的版本方法进行调用。
#java#