![](/img/trans.png)
[英]Java Bytecode: invokevirtual methodref on an objectref with a different class
[英]Java bytecode operation 'invokevirtual' does not keep consistency for the methods inherited by Object
我有以下代碼。
public class Parent {
@Override
public int hashCode() {
return 0;
}
}
public class Child extends Parent {
public void test() {
this.toString();
this.hashCode();
}
}
正如您在上面的代碼中看到的那樣,Child從Object繼承toString(),從Parent繼承hashCode()。 Child#test的字節碼操作如下。
ALOAD 0: this
INVOKEVIRTUAL Object.toString() : String
ALOAD 0: this
INVOKEVIRTUAL Child.hashCode() : int
RETURN
我認為如果invokevirtual調用Object.toString(),它應該調用Parent.hashCode()以保持一致性。 或者,調用Child.hashCode(),然后調用Child.toString()。
但是,當且僅當Object繼承目標方法時,invokevirtual才會保持其一致性。
只有這種情況,在Object中調用虛擬調用方法。 對於其他情況,請在當前類中調用虛擬調用方法。
我想知道為什么會這樣。
編譯器不知道類實例的內部布局。 相反,它生成對實例方法的符號引用,這些引用存儲在運行時常量池中。 在運行時解析這些運行時常量池項以確定實際的方法位置。
這意味着所有這些符號的Child.hashCode()
只是常量,它們沒有指定JVM如何調用此方法。 看來, toString()
方法編譯器知道,這個方法在Object
類中有它的基本實現,所以它將常量池中的符號常量放到Object
類中 - 這是一種優化,這使得JVM的編譯器:
Constant pool:
const #2 = Method #24.#25; // java/lang/Object.toString:()Ljava/lang/String;
...
const #24 = class #34; // java/lang/Object
const #25 = NameAndType #35:#36;// toString:()Ljava/lang/String;
你是正確的,編譯器的行為是非邏輯的。 但是此代碼的效果與您建議的兩種變體的效果相同。 所以這可能不是故意的行為,而是編譯器代碼長期演變的結果。 其他編譯器可能會生成不同的代碼。
我的理論: toString()
經常使用,因此javac使用公共Object.toString()
來保存常量池條目。
例如,如果代碼包含foo.toString() and bar.toString()
,則只需要一個Object.toString
,而不是兩個條目Foo.toString and Bar.toString
Javac可能硬編碼了這種優化,而不是分析代碼以確定它是否真的需要。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.