Java中的静态分派与动态分派
2015-10-07 19:22
295 查看
本文是《深入理解Java虚拟机》8.3.2节的读书笔记,理解有误的地方,欢迎指正
首先是两个概念:
静态类型,即是变量声明时的类型。
实际类型,变量实例化时采用的类型。
比如我们有这样一段代码
我们就称变量 man 的静态类型为 Human,实际类型为 Man。
静态分派发生在编译阶段,因此确定静态分派的动作实际上不是由虚拟机执行的。
比如这样一段代码,其中man和woman的静态类型都是Human,但是实际类型各异:
编译过后所得的字节码文件如下:
在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方法重写的本质。
首先是两个概念:
静态类型,即是变量声明时的类型。
实际类型,变量实例化时采用的类型。
比如我们有这样一段代码
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方法重写的本质。
相关文章推荐
- Java泛型T的应用 获取T类型
- hadoop_hbase Java API 介绍及使用示例
- 从道德经中学习java
- java 集合学习笔记
- Struts2复习笔记
- eclipse常用快捷键
- Java虚拟机12:Java内存模型
- java中ArrayList 、LinkList区别
- Java版双向链表实现
- MyEclipse8.5下修改字体大小
- Java学习中遇到的一些问题
- 大龄屌丝自学笔记--Java零基础到菜鸟--027
- Java的一些经验讲述
- Java编程中应用的GUI设计基础
- Java中的equals和hashCode方法详解
- myeclipse 使用JDBC方法直接访问sql2005
- java中常犯的几个错误,自勉。
- (转)Struts2返回JSON对象的方法总结
- java.lang.RuntimeException: Unable to start activity ComponentInfo{包名/类名}
- SpringMVC的原理、搭建和应用(一)