今天看啥  ›  专栏  ›  gzss

java方法调用-动态分派

gzss  · 简书  ·  · 2019-07-09 23:19

前一篇文章聊了一下方法调用中的静态分派,这次我们聊下动态分派的过程,他和多态的另外一个重要体现-重写(override)有着很密切的关联。先看下如下代码:


运行结果:
man say hello

woman say hello

woman say hello

这个运行结果对于习惯了面向对象思维的java程序员来说相对比较简单。但是虚拟机是如何知道要调用哪个方法呢?

显然不能根据静态类型来决定,因为金泰类型同样都是human的两个变量man和woman在调用sayHello()方法时执行了不同的行为,并且变量man在两次调用中执行了不同的方法。导致这个现象的原因很明显,是这两个变量的实际类型不同,Java虚拟机是如何根据实际类型来分派方法执行版本呢,我们通过javap命令输出这段代码的字节码,参见下图:


0-15行的字节码是准备动作,作用是建立man和woman的内存空间、调用Man和Woman类的实例构造器,将这两个实例的引用存放在第1、2个局部变量表Slot之中,这个动作也对应代码中的这两句:
Human man = new Man();

Human woman = new Woman();

接下来16-21句是关键部分,16、20分别把刚刚建立的两个对象的引用压倒栈顶,这两个对象时将要执行sayHello()方法的所有者,称为接收者;17和21句是方法的调用指令,这两条调用指令从字节码角度来看,无论指令还是参数完全一样,但是这两句指令最终执行的目标方法并不相同。原因就需要从invokevirtual指令的多态查找过程开始说起,invokevirtual指令的运行时解析过程大致分为以下几个步骤:

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

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

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

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

由于invokevirtual指令执行的第一步就是在运行期确定接收者的实际类型,所以两次调用中invokevirtual指令吧常量池中的类方法符号引用解析到了不同的直接引用上,这个过程就是java语言中方法重写的本质。我们把这种在运行期根据实际类型确定方法执行版本的分派过程称为动态分派。




原文地址:访问原文地址
快照地址: 访问文章快照