在上两篇中分别对方法重载【https://www.cnblogs.com/webor2006/p/9723289.html】和方法重写【https://www.cnblogs.com/webor2006/p/9797506.html】在字节码中的表现进行了详细的分析,其中得出如下结论:方法重载是静态的,是编译期行为;方法重写是动态的,是运行期行为。
这次继续来举一个综合的例子,既有方法重载又有方法重写,进一步来阐述其静态分派与动态分派的机制,如下:
那其结果是啥呢?咱们直接运行一下:
分析一下:
咱们来看一下该代码对应的字节码信息:
其实针对于方法调用动态分派的过程,虚拟机会在类的方法区建立一个虚方法表的数据结构(virtual method table,也简称vtable)。针对于invokeinterface指令来说,虚拟机会建议一个叫做接口方法的数据结构(interface method table,也简单itable),其查找机制基本类似,下面用一个示例图来对其进行理解:
其中虚方法表vtable中每一项都存放的是特定方法实际真正的入口调用地址,其中有一种情况,就是子类Dog只继承了Animal但没有重写过Animal父类的方法,如下:
如果Dog子类重写了父类的方法,那么当然方法就会存在于Dog的虚方法表中啦。所以说对于Object类来说里面定义了很多的方法,但是实际我们编写的类可能很多都没有重写它里面的方法,那么其虚方法表中都是存在Object当中而非拷贝一份到我们具体子类当中。
另外虚方法表vtable还有一点就是:只要是子类和父类的方法描述是一样的,那么它们在父类和子类的索引是一样的,这样当查找子类的方法时,由于索引跟父类是一模一样的,则直接拿着子类该方法的索引到父类的方法表中的对应的索引就直接可以定位到了,比较高效。一般虚方法表都是在类的连接阶段【类加载有加载、连接、初始化阶段】进行的初始化。
下面来看一下这个程序,比较容易犯错误,看一下伪代码:
如果说这样调用:
肯定是木有问题的,很显然就是Child的一个静态调用,那如果这样调用呢?
这个结果是编译都通不过,不信的话咱们以之前的例子稍加修改一下:
为啥呢?其实这个从字节码上就能够解释,对于这个程序:
其实会对应于字节码的invokevirtural指令,是静态行为,其参数为Parent.test3(),就类似于:
而Parent类很明显没有定义test3()这个方法嘛,当然就编译不过了。