静态分派与动态分派——记一次被腾讯面试官暴虐的面试经历
在腾讯的面试中被问到了重载的运行时多态是怎么实现的,显然这一块是我的知识盲区,所以赶紧补充下。
静态分派
依赖静态类型来定位方法执行版本的分派动作称作静态分派,静态分派的典型是方法重载。如下代码实例:
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);//hello,guy!
sr.sayHello(woman);//hello,guy!
}
}
对于这段代码的运行结果想必大家都很清楚,究其原因,重载的方法调用是由静态类型而非实际类型决定的,在编译器便决定了使用哪个重载版本,并会把这个方法的符号引用写到main()方法的两条invokevirtual指令的参数中。
总结:Java重载是基于静态分派完成的。
动态分派
我们知道Java多态性另一种实现方式是“重写”,而这种运行时多态编译器是不可能知道它的实际调用的实例的,如下代码所示:
Scanner in = new Scanner(System.in);
Person person = null;
if (in.nextLine().equals("chinese")) {
person = new Chinese();
} else {
person = new English();
}
person.sayHello();
在重载的场景下,JVM使用的是动态分派,即在运行时确定接受者的实际类型。JVM会在操作数栈中找到指向对象的实际类型,之后在该类的方法表(存在方法区中,存放着各个方法的实际入口)中找到对应描述符和和简单名称都相符的方法,同时进行访问权限校验,如通过则查找过程结束。如果没有找到匹配的方法,则通过继承关系在该类的父类的方法表中查找,查找到则结束,否则抛异常。
参考资料
多态在 Java 和 C++ 编程语言中的实现比较
《深入理解Java虚拟机》