Java中的动态绑定在运行时发生,用于覆盖函数。我想知道它是如何在内部发生的(比如在C ++中,使用虚函数/表)。
答案 0 :(得分:4)
Java中的所有非最终非私有非静态方法都是虚拟的
答案 1 :(得分:2)
如上所述,在Java中,所有非最终的非私有,非静态方法都是虚拟的,这意味着可以在子类中重写的任何方法都是虚拟的。
但是为了了解JVM内部处理方式,让我们以下面的代码示例为例
public class OverridingInternalExample {
private static class Mammal {
public void speak() { System.out.println("ohlllalalalalalaoaoaoa"); }
}
private static class Human extends Mammal {
@Override
public void speak() { System.out.println("Hello"); }
// Valid overload of speak
public void speak(String language) {
if (language.equals("Hindi")) System.out.println("Namaste");
else System.out.println("Hello");
}
@Override
public String toString() { return "Human Class"; }
}
// Code below contains the output and and bytecode of the method calls
public static void main(String[] args) {
Mammal anyMammal = new Mammal();
anyMammal.speak(); // Output - ohlllalalalalalaoaoaoa
// 10: invokevirtual #4 // Method org/programming/mitra/exercises/OverridingInternalExample$Mammal.speak:()V
Mammal humanMammal = new Human();
humanMammal.speak(); // Output - Hello
// 23: invokevirtual #4 // Method org/programming/mitra/exercises/OverridingInternalExample$Mammal.speak:()V
Human human = new Human();
human.speak(); // Output - Hello
// 36: invokevirtual #7 // Method org/programming/mitra/exercises/OverridingInternalExample$Human.speak:()V
human.speak("Hindi"); // Output - Namaste
// 42: invokevirtual #9 // Method org/programming/mitra/exercises/OverridingInternalExample$Human.speak:(Ljava/lang/String;)V
}
}
我们可以看到anyMammal.speak()
和humanMammal.speak()
的字节码是相同的(invokevirtual #4 // Method org/programming/mitra/exercises/OverridingInternalExample$Mammal.speak:()V
),因为根据编译器,这两种方法都是在哺乳动物参考上调用的。
现在问题来了,如果两个方法调用都具有相同的字节码,那么JVM如何知道要调用哪个方法?
好吧,根据JVM规范,答案隐藏在字节码本身中,并且是invokevirtual
指令
invokevirtual调用对象的实例方法,并根据对象的(虚拟)类型进行分派。这是Java编程语言中的常规方法分派。
JVM使用invokevirtual
指令来调用等效于C ++虚拟方法的Java。在C ++中,如果我们想覆盖另一个类中的一个方法,则需要将其声明为virtual
,但是在Java中,默认情况下,所有方法都是虚拟的(final和static方法除外),因为我们可以覆盖子级中的每个方法类。
操作invokevirtual
接受一个指向方法引用调用的指针(#4
进入常量池的索引)
invokevirtual #4 // Method org/programming/mitra/exercises/OverridingInternalExample$Mammal.speak:()V
该方法引用#4再次引用方法名称和类引用
#4 = Methodref #2.#27 // org/programming/mitra/exercises/OverridingInternalExample$Mammal.speak:()V
#2 = Class #25 // org/programming/mitra/exercises/OverridingInternalExample$Mammal
#25 = Utf8 org/programming/mitra/exercises/OverridingInternalExample$Mammal
#27 = NameAndType #35:#17 // speak:()V
#35 = Utf8 speak
#17 = Utf8
所有这些引用共同用于获取对要在其中找到该方法的方法和类的引用。 JVM规范
中也提到了这一点。Java虚拟机不要求对象4具有任何特定的内部结构。
书签4个状态
在Oracle对Java虚拟机的某些实现中,对类实例的引用是指向本身是一对指针的句柄的指针:一个指向包含对象的方法的表和指向Class的指针表示对象类型的对象,另一个表示从堆为对象数据分配的内存。
这意味着每个引用变量都包含两个隐藏的指针
但是问题又来了,invokevirtual
在内部如何做到这一点?嗯,没人能回答这个问题,因为它取决于JVM的实现,并且因JVM而异。
从上面的陈述中,我们可以得出结论,对象引用间接持有一个表的引用/指针,该表保存了该对象的所有方法引用。 Java从C ++借用了这个概念,并且该表以各种名称(例如virtual method table ( VMT ), virtual function table ( vftable ), virtual table ( vtable ), dispatch table)而闻名。
我们不确定vtable
是如何在Java中实现的,因为它依赖于JVM。但是我们可以预料它将采用与C ++相同的策略,其中vtable
是类似数组的结构,该结构将方法名及其引用保留在数组索引上。每当JVM尝试执行虚拟方法时,它总是向vtable
询问其地址。
每个类只有一个vtable
,这意味着它对于与Class
对象相似的类中的所有对象都是唯一的。我在文章Why an outer Java class can’t be static和Why Java is Purely Object-Oriented Language Or Why Not中讨论了Class
对象的更多信息。
因此vtable
类只有一个Object
,其中包含所有11个方法(如果不计算registerNatives的话)以及对它们各自方法主体的引用。
当JVM将Mammal
类加载到内存中时,它会为其创建一个Class
对象,并创建一个vtable
,其中包含Object类的vtable中所有具有相同引用的方法(因为Mammal
不会覆盖Object中的任何方法,而是为speak
方法添加了一个新条目。
现在轮到Human
类了,现在JVM会将所有条目从vtable
类的Mammal
复制到vtable
的{{1}},然后为Human
的重载版本添加新条目。
JVM知道speak(String)
类重写了两种方法,一种是Human
中的toString()
,第二种是Object
中的speck()
。现在,而不是使用更新的引用为这些方法创建新条目。 JVM将修改对早先存在的相同索引上已经存在的方法的引用,并将保留相同的方法名称。
Mammal
使JVM将方法引用invokevirtual
处的值视为地址,而是作为要在#4
中查找当前对象的方法的名称。
我希望现在弄清楚JVM如何将vtable
条目和constant pool
混合在一起以得出将要调用的方法的结论。
您可以阅读我的文章How Does JVM Handle Method Overloading and Overriding Internally的更多信息。
答案 2 :(得分:0)
JVM规范描述了JVM必须如何解决虚拟方法:http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.invokevirtual
这部分是最相关的:
否则,如果C具有超类,则使用C的直接超类递归地执行相同的查找过程;要调用的方法是递归调用此查找过程的结果。
究竟如何进行递归取决于JVM编写器如何实现它。这是一个描述如何在OpenJDK中完成的链接(包括指向C ++代码的链接): https://wikis.oracle.com/display/HotSpotInternals/VirtualCalls