我正在阅读这篇文章,了解 JVM 如何调用方法,我想我已经了解了大部分内容。但是,我仍然无法理解
invokeinterface
的必要性。
按照我的理解,一个类基本上有一个方法的虚拟表,当使用
invokevirtual
或 invokeinterface
调用方法时,会查阅该虚拟表。
那么,在接口上定义的方法和在基类上定义的方法有什么区别呢?为什么字节码不同?
说明的描述看起来也非常相似。
文章似乎声称,每次调用方法时,接口的方法表都可以有“不同的偏移量”。我不明白的是为什么接口会有一个方法表,因为没有对象可以将接口作为其实际类型。
我错过了什么?
每个 Java 类都与一个“虚拟方法表”相关联,其中包含指向类的每个方法的字节码的“链接”。该表继承自特定类的超类,并根据子类的新方法进行扩展。例如,
class BaseClass {
public void method1() { }
public void method2() { }
public void method3() { }
}
class NextClass extends BaseClass {
public void method2() { } // overridden from BaseClass
public void method4() { }
}
结果见表
基类 1. 基类/方法1() 2. 基类/方法2() 3. 基类/方法3() 下一堂课 1. 基类/方法1() 2.NextClass/method2() 3. 基类/方法3() 4.NextClass/方法4()
注意,
NextClass
的虚拟方法表如何保留
BaseClass
表的条目顺序,并仅覆盖它覆盖的method2()
的“链接”。JVM 的实现可以通过记住 invokevirtual
始终是调用此方法的任何对象的虚拟方法表中的第三个条目来优化对
BaseClass/method3()
的调用。使用 invokeinterface
这种优化是不可能的。例如,
interface MyInterface {
void ifaceMethod();
}
class AnotherClass extends NextClass implements MyInterface {
public void method4() { } // overridden from NextClass
public void ifaceMethod() { }
}
class MyClass implements MyInterface {
public void method5() { }
public void ifaceMethod() { }
}
这个类层次结构产生了虚拟方法表
另一类 1. 基类/方法1() 2.NextClass/method2() 3. 基类/方法3() 4.另一个类/方法4() 5. MyInterface/ifaceMethod() 我的课 1.MyClass/method5() 2.MyInterface/ifaceMethod()
如您所见,
AnotherClass
在其第五个条目中包含接口的方法,而
MyClass
在其第二个条目中包含它。要真正在虚拟方法表中找到正确的条目,对具有 invokeinterface
的方法的调用将始终必须搜索完整的表,而没有机会进行 invokevirtual
所做的优化样式。还有其他区别,例如 invokeinterface
可以与实际上未实现接口的对象引用一起使用。因此,
invokeinterface
必须在运行时检查表中是否存在方法,并可能引发异常。