Java的动态分派和静态分派的实现

2020-03-09 16:01:30王振洲
invokespecfical 调用对象的构造函数和私有方法 invokevirtual 调用对象的 public/protected 的方法 可能通过继承复写的方法称做 virtual method: 表示要到运行时才能定位到真正的方法实现。通过符号引用确定虚方法直接引用的过程又叫做动态分派 invokeinterface 调用接口的方法 具体的实现类将在调用时确定 invokedynamic JDK1.7 为了让 JVM 支持动态类型语言引入的指令 让用户可以决定如何查找目标方法

符号引用到直接引用

由于 Java 的编译没有C C++ 编译过程中的链接阶段,所以 Class 文件中储存的只是符号引用,等到了在运行时才通过符号引用定位到方法区中方法代码在内存布局中的位置--直接引用。
符号引用到直接引用的替换又涉及两种方式。一种是解析,另一种是分派。解析发生在类加载的解析阶段,分派发生在编译或方法调用阶段。

解析

在类加载的解析阶段会把满足「编译期可知,运行期不可变」的方法的符号引用替换为指向方法区的直接引用,不会延迟到运行时再去完成。
满足编译期可知,运行期不可变的方法有:构造函数、私有方法、静态方法、final修饰的方法。不满足上述条件的方法的符号引用替换发生在方法调用期间。

分派 Dispatch

多态的实现原理

变量类型
理解分派之前,需要先看两个类型概念。
比如:Object obj = new String("");

静态类型

定义变量时,声明的类型。比如这里 obj 的静态类型就是 Object。静态类型在编译期的编译器就能知道。

实际类型

变量赋值时的实际类型。比如这里 obj 的实际类型就是 String。实际类型在编译期的编译器是不可知的。

静态分派

根据变量的「静态类型(外观类型)」匹配调用方法的过程称为静态分派。发生的场景为方法重载。
如下代码:

public class StaticDispatch {

 static abstract class Human { }
 static class Man extends Human { }
 static class Woman extends Human { }
 static class Child extends Human { }

 public void say(Human human) {
  System.out.println("human");
 }

 public void say(Man man) {
  System.out.println("man");
 }

 public void say(Woman woman) {
  System.out.println("woman");
 }

 public void say(Child child) {
  System.out.println("child");
 }
}

public static void main(String[] args) {
 Human man = new Man();
 Human woman = new Woman();
 Human child = new Child();

 StaticDispatch dispatch = new StaticDispatch();
 dispatch.say(man);
 dispatch.say(woman);
 dispatch.say(child);
}