![](/img/trans.png)
[英]Java bytecode operation 'invokevirtual' does not keep consistency for the methods inherited by Object
[英]Why does the Java compiler 11 use invokevirtual to call private methods?
當使用 OpenJDK 8 中的 Java 編譯器編譯下面的代碼時,對foo()
的調用是通過invokespecial
完成的,但是當使用 OpenJDK 11 時,會發出一個invokevirtual
。
public class Invoke {
public void call() {
foo();
}
private void foo() {}
}
使用javac
1.8.0_282 時javap -v -p
的 Output :
public void call();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #2 // Method foo:()V
4: return
使用javac
11.0.10 時javap -v -p
的 Output :
public void call();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokevirtual #2 // Method foo:()V
4: return
我不明白為什么在這里使用invokevirtual
因為不能覆蓋foo()
。
經過一番挖掘,似乎在私有方法上調用虛擬的目的是允許嵌套類從外部invokevirtual
調用私有方法。 所以我嘗試了下面的代碼:
public class Test{
public static void main(String[] args) {
// Build a Derived such that Derived.getValue()
// somewhat "exists".
System.out.println(new Derived().foo());
}
public static class Base {
public int foo() {
return getValue() + new Nested().getValueInNested();
}
private int getValue() {
return 24;
}
private class Nested {
public int getValueInNested() {
// This is getValue() from Base, but would
// invokevirtual call the version from Derived?
return getValue();
}
}
}
public static class Derived extends Base {
// Let's redefine getValue() to see if it is picked by the
// invokevirtual from getValueInNested().
private int getValue() {
return 100;
}
}
}
用 11 編譯這段代碼,我們可以在javap
的invokevirtual
中看到,在foo()
和getValueInNested()
中都使用了 invokevirtual:
public int foo();
descriptor: ()I
flags: (0x0001) ACC_PUBLIC
Code:
stack=4, locals=1, args_size=1
0: aload_0
// ** HERE **
1: invokevirtual #2 // Method getValue:()I
4: new #3 // class Test$Base$Nested
7: dup
8: aload_0
9: invokespecial #4 // Method Test$Base$Nested."<init>":(LTest$Base;)V
12: invokevirtual #5 // Method Test$Base$Nested.getValueInNested:()I
15: iadd
16: ireturn
public int getValueInNested();
descriptor: ()I
flags: (0x0001) ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: getfield #1 // Field this$0:LTest$Base;
// ** HERE **
4: invokevirtual #3 // Method Test$Base.getValue:()I
7: ireturn
所有這些都有點令人困惑,並提出了一些問題:
invokevirtual
用來調用私有方法? 是否存在用invokespecial
替換它的用例不等效?Nested.getValueInNested()
中對getValue()
的調用如何不從Derived
中選擇方法,因為它是通過invokevirtual
調用的?這是作為https://openjdk.java.net/jeps/181的一部分完成的:基於嵌套的訪問控制,因此 JVM 可以允許從嵌套類訪問私有方法。
在此更改之前,編譯器必須在嵌套 class 調用的Base
class 中生成一個受包保護的合成方法。 該合成方法將依次調用Base
class 中的私有方法。 Java 11 中的功能增強了 JVM 以允許編譯器不必生成合成方法。
關於invokevirtual
是否會調用Derived
class 中的方法這一點,答案是否定的。 私有方法仍然不受運行時 class 的方法選擇的影響(這從未改變過):
在執行
invokeinterface
或invokevirtual
指令期間,根據(i)堆棧上object的運行時類型和(ii)先前由指令解析的方法來選擇方法。 select 一個方法關於 class 或接口 C 和一個方法 m ZE1E1D3D405731283DEECEE6的規則如下:
- 如果 m R標記為
ACC_PRIVATE
,則它是選定的方法。
編輯:
基於評論“如果私有方法是從方法所有者 class 調用的,並且如果從嵌套的 class 調用它,則使用 invokevirtual 是否仍然有效?”
正如 Holger 提到的,是的,它是有效的,但是基於JEP ,我猜為了簡單起見,決定切換到invokevirtual
(雖然我無法證實這一點,這只是一個猜測):
隨着訪問規則的改變,以及對字節碼規則的適當調整,我們可以允許生成調用字節碼的簡化規則:
- invokespecial 用於私有的 nestmate 構造函數,
- invokevirtual 用於私有非接口,nestmate 實例方法,
- 私有接口的invokeinterface,nestmate實例方法; 和
- 私人巢友的invokestatic,static方法
來自JDK-8197445 的另一個有趣的注釋:JEP 181 的實現:基於嵌套的訪問控制:
傳統上,
invokespecial
用於調用private
成員,盡管invokevirtual
也具有此功能。 我們不需要在不同的 class 中調用private
方法來使用invokevirtual
,而不是擾亂由invokespecial
強制執行的有關超類型的復雜規則。
認為在已經提供和接受的答案中添加更多信息是合適的,盡管這不是絕對必要的,但它可能有助於擴大理解,因此它符合 SO 用戶的最大利益。
在早期版本中,在 Java 11 之前,正如@ma在接受的答案中已經指出的那樣,編譯器需要創建橋接方法以允許類在這種情況下訪問彼此的私有成員。 這些可訪問性擴大的橋接方法在執行上下文中被調用,編譯器將代碼插入到正在運行的程序中。
這樣做會增加已部署應用程序的大小並增加復雜性,而且更難理解幕后發生的事情。
Java 11引入了基於嵌套的訪問控制的概念。 除了嵌套對象的概念和JVM中的相關訪問規則外,這還允許類和接口相互嵌套。
嵌套類型可以是私有字段、方法和構造函數。
使用更新的反射 API 您現在可以查詢有關基於嵌套的訪問控制功能的信息。
Java 11 中的一些新優點
getNestHost()
方法用於獲取巢主機的名稱, isNestmateOf()
方法可用於檢查class是否為巢友。 此外, getNestMembers()
方法返回一個嵌套成員數組。
這是一個通用示例的鏈接,由 Baeldung.com 提供,基於巢的訪問控制使得好處非常突出恕我直言。
請注意,反匯編代碼中沒有編譯器生成的橋接方法。 此外,在上面鏈接的示例中,內部 class 現在可以直接調用 outerPrivate() 方法。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.