java方法调用之动态调用多态(重写override)的实现原理 |
您所在的位置:网站首页 › override怎么调用出来 › java方法调用之动态调用多态(重写override)的实现原理 |
上两篇篇博文讨论了java的重载(overload)与重写(override)、静态分派与动态分派,这篇博文讨论下动态分派的实现方法,即多态override的实现原理。 java方法调用之重载、重写的调用原理(一) java方法调用之单分派与多分派(二) 本文大部分内容来自于IBM的博文多态在 Java 和 C++ 编程语言中的实现比较 。这里写一遍主要是加深自己的理解,方便以后查看,加入了一些自己的见解及行文组织,不是出于商业目的,如若需要下线,请告知。 结论基于基类的调用和基于接口的调用,从性能上来讲,基于基类的调用性能更高 。因为invokevirtual是基于偏移量的方式来查找方法的,而invokeinterface是基于搜索的。 概述多态是面向对象程序设计的重要特性。多态允许基类的引用指向派生类的对象,而在具体访问时实现方法的动态绑定。 java对方法动态绑定的实现方法主要基于方法表,但是这里分两种调用方式invokevirtual和invokeinterface,即类引用调用和接口引用调用。类引用调用只需要修改方法表的指针就可以实现动态绑定(具有相同签名的方法,在父类、子类的方法表中具有相同的索引号),而接口引用调用需要扫描整个方法表才能实现动态绑定(因为,一个类可以实现多个接口,另外一个类可能只实现一个接口,无法具有相同的索引号。这句如果没有看懂,继续往下看,会有例子。写到这里,感觉自己看书时,有的时候也会不理解,看不懂,思考一段时间,还是不明白,做个标记,继续阅读吧,然后回头再看,可能就豁然开朗。)。 类引用调用的大致过程为:java编译器将java源代码编译成class文件,在编译过程中,会根据静态类型将调用的符号引用写到class文件中。在执行时,JVM根据class文件找到调用方法的符号引用,然后在静态类型的方法表中找到偏移量,然后根据this指针确定对象的实际类型,使用实际类型的方法表,偏移量跟静态类型中方法表的偏移量一样,如果在实际类型的方法表中找到该方法,则直接调用,否则,按照继承关系从下往上搜索。 下面对上面的描述做具体的分析讨论。 JVM的运行时结构
代码如下: package org.fan.learn.methodTable; /** * Created by fan on 2016/3/30. */ public class ClassReference { static class Person { @Override public String toString(){ return "I'm a person."; } public void eat(){ System.out.println("Person eat"); } public void speak(){ System.out.println("Person speak"); } } static class Boy extends Person{ @Override public String toString(){ return "I'm a boy"; } @Override public void speak(){ System.out.println("Boy speak"); } public void fight(){ System.out.println("Boy fight"); } } static class Girl extends Person{ @Override public String toString(){ return "I'm a girl"; } @Override public void speak(){ System.out.println("Girl speak"); } public void sing(){ System.out.println("Girl sing"); } } public static void main(String[] args) { Person boy = new Boy(); Person girl = new Girl(); System.out.println(boy); boy.eat(); boy.speak(); //boy.fight(); System.out.println(girl); girl.eat(); girl.speak(); //girl.sing(); } }注意,boy.fight(); 和 girl.sing(); 这两个是有问题的,在IDEA中会提示“Cannot resolve method ‘fight()’”。因为,方法的调用是有静态类型检查的,而boy和girl的静态类型都是Person类型的,在Person中没有fight方法和sing方法。因此,会报错。 执行结果如下: 其中所有的invokevirtual调用的都是Person类中的方法。 下面看看java对象的内存模型: 下面再看看调用过程,以girl.speak() 方法的调用为例。在我的字节码中,这条指令对应43: invokevirtual #9; //Method ClassReference$Person.speak:()V ,为了便于使用IBM的图,这里采用跟IBM一致的符号引用:invokevirtual #12; 。调用过程图如下所示: 代码如下: package org.fan.learn.methodTable; /** * Created by fan on 2016/3/29. */ public class InterfaceReference { interface IDance { void dance(); } static class Person { @Override public String toString() { return "I'm a person"; } public void speak() { System.out.println("Person speak"); } public void eat() { System.out.println("Person eat"); } } static class Dancer extends Person implements IDance { @Override public String toString() { return "I'm a Dancer"; } @Override public void speak() { System.out.println("Dancer speak"); } public void dance() { System.out.println("Dancer dance"); } } static class Snake implements IDance { @Override public String toString() { return "I'm a Snake"; } public void dance() { System.out.println("Snake dance"); } } public static void main(String[] args) { IDance dancer = new Dancer(); System.out.println(dancer); dancer.dance(); //dancer.speak(); //dancer.eat(); IDance snake = new Snake(); System.out.println(snake); snake.dance(); } }上面的代码中dancer.speak(); dancer.eat(); 这两句同样不能调用。 执行结果如下所示: 从上面的字节码指令可以看到,dancer.dance(); 和snake.dance(); 的字节码指令都是invokeinterface #6, 1; //InterfaceMethod InterfaceReference$IDance.dance:()V 。 为什么invokeinterface指令会有两个参数呢? 对象的内存模型如下所示: 下面写一个,如果Dancer中没有重写(override)toString方法,会发生什么? 代码如下: package org.fan.learn.methodTable; /** * Created by fan on 2016/3/29. */ public class InterfaceReference { interface IDance { void dance(); } static class Person { @Override public String toString() { return "I'm a person"; } public void speak() { System.out.println("Person speak"); } public void eat() { System.out.println("Person eat"); } } static class Dancer extends Person implements IDance { // @Override // public String toString() { // return "I'm a Dancer"; // } @Override public void speak() { System.out.println("Dancer speak"); } public void dance() { System.out.println("Dancer dance"); } } static class Snake implements IDance { @Override public String toString() { return "I'm a Snake"; } public void dance() { System.out.println("Snake dance"); } } public static void main(String[] args) { IDance dancer = new Dancer(); System.out.println(dancer); dancer.dance(); //dancer.speak(); //dancer.eat(); IDance snake = new Snake(); System.out.println(snake); snake.dance(); } }执行结果如下: 这篇博文讨论了invokevirtual和invokeinterface的内部实现的区别,以及override的实现原理。下一步,打算讨论下invokevirtual的具体实现细节,如:如何实现符号引用到直接引用的转换的?可能会看下OpenJDK底层的C++实现。 参考资料 周志明 《深入理解JAVA虚拟机》IBM 多态在 Java 和 C++ 编程语言中的实现比较 |
今日新闻 |
推荐新闻 |
CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3 |