先看下面这一段代码:
public class DynamicDispatching {
abstract class Animal{
abstract void run();
}
class Cat extends Animal{
@Override
void run() {
System.out.println("Cat run.");
}
}
class Dog extends Animal{
@Override
void run() {
System.out.println("Dog run.");
}
}
public static void main(String[] args) {
DynamicDispatching dynamicDispatching = new DynamicDispatching();
Animal cat = dynamicDispatching.new Cat();
cat.run();
Animal dog = dynamicDispatching.new Dog();
dog.run();
}
}
相信这段程序的输出结果不会出乎任何熟悉面向对象的程序员的意料:
产生这个结果的原因是虚拟机是根据变量的实际类型来判断调用哪一个重写的方法。也就是说,
在重写时,是通过变量的实际类型来判断使用哪一个方法。
前面介绍了动态分派的过程,而虚拟机是具体是怎么做到动态分派的呢?
由于动态分派是一个非常频繁的操作,而动态分派的方法版本选择需要运行时在类的方法元数据中搜索合适的目标方法,因此虚拟机的大部分实现中出于性能考虑都不会真正地进行如此频繁的搜索。
为了解决这个问题,最稳定的解决方法就是在方法区中为类建一个虚方法表:
虚方法表中存储了各个方法的实际入口地址,(子类的虚方法表中)子类没有重写的方法指向和父类一致的地址,被子类重写了的方法(子类的虚方法表中)会被替换为指向子类版本方法的入口地址。
虚方法表一般在类加载的连接阶段进行初始化,虚拟机在准备了类变量的初始值后就会把类的虚方法表也初始化完毕。