您的位置:首页 > 编程语言 > Java开发

Java中的静态分派与动态分派

2015-10-07 19:22 295 查看
本文是《深入理解Java虚拟机》8.3.2节的读书笔记,理解有误的地方,欢迎指正

首先是两个概念:

静态类型,即是变量声明时的类型。

实际类型,变量实例化时采用的类型。

比如我们有这样一段代码

class Human {}
public class Man extends Human {
public static void main(String[] args) {
Human man = new Man();
}
}


我们就称变量 man 的静态类型为 Human,实际类型为 Man。

静态分派

所有依赖静态类型来定位方法执行版本的分派动作称为静态分派,其典型应用是方法重载(根据参数的静态类型来定位目标方法)。

静态分派发生在编译阶段,因此确定静态分派的动作实际上不是由虚拟机执行的。

动态分派

在运行期根据实际类型确定方法执行版本。

比如这样一段代码,其中man和woman的静态类型都是Human,但是实际类型各异:

public class DynamicDispatch {
public static void main(String[] args) {
Human man = new Man();
Human woman = new Woman();
man.sayHello();        // Output : man say hello
woman.sayHello();      // Output : woman say hello
}
private static abstract class Human {
protected abstract void sayHello();
}
private static class Man extends Human {
protected void sayHello() {
System.out.println("man say hello");
}
}
private static class Woman extends Human {
protected void sayHello() {
System.out.println("woman say hello");
}
}
}


编译过后所得的字节码文件如下:

(常量池的部分内容)
Constant pool:
#1 = Methodref          #8.#23         // java/lang/Object."<init>":()V
#2 = Class              #24            // DynamicDispatch$Man
#3 = Methodref          #2.#25         // DynamicDispatch$Man."<init>":(LDynamicDispatch$1;)V
#4 = Class              #26            // DynamicDispatch$Woman
#5 = Methodref          #4.#25         // DynamicDispatch$Woman."<init>":(LDynamicDispatch$1;)V
#6 = Methodref          #13.#27        // DynamicDispatch$Human.sayHello:()V


public class DynamicDispatch {
public DynamicDispatch();
Code:
0: aload_0
1: invokespecial #1                  // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: new           #2                  // class DynamicDispatch$Man
3: dup
4: aconst_null
5: invokespecial #3                  // Method DynamicDispatch$Man."<init>":(LDynamicDispatch$1;)V
8: astore_1
9: new           #4                  // class DynamicDispatch$Woman
12: dup
13: aconst_null
14: invokespecial #5                  // Method DynamicDispatch$Woman."<init>":(LDynamicDispatch$1;)V
17: astore_2
18: aload_1                        // 将第二个引用类型本地变量(man)推送至操作数栈栈顶
19: invokevirtual #6     // Method DynamicDispatch$Human.sayHello:()V,调用#6代表的实例方法,并
c4fb
且方法的接收者就是操作数栈顶元素
22: aload_2
23: invokevirtual #6                  // Method DynamicDispatch$Human.sayHello:()V
26: return
}


在main函数中,0~8是创建Man对象并赋到man,9~17是创建woman对象并赋到woman,18就是将man对象压入操作数栈栈顶,接下来19调用Human的sayHello方法,执行的字节码指令是invokevirtual,其具有多态查找过程,在运行时的解析过程大致为:

 找到操作数栈顶的第一个元素所指向的对象的实际类型,记为C。

 如果在类型C中找到与常量中的描述符合简单名称都相符的方法,则进行访问权限校验,如果通过则返回这个方法的直接引用,查找过程结束;如果权限校验不通过,返回java.lang.IllegalAccessError异常。

 否则,按照继承关系从下往上一次对C的各个父类进行第2步的搜索和验证过程。

 如果始终没有找到合适的方法,则抛出 java.lang.AbstractMethodError异常。

这里方法的接收者——即操作数栈栈顶元素是man对象,最后的结果就是调用了Man中的sayHello方法。同样的,对woman对象的方法调用也是如此,最后执行的是Woman中的sayHello方法。

由于invokevirtual指令执行的第一步就是在运行期间确定接收者的实际类型,所以两次调用中的invokevirtual指令把常量池中的类方法符号引用解析到了不同的直接引用上,这个过程就是Java方法重写的本质。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: