[英]Inner class access field before java-11
有這樣的 class:
public class Sample1 {
public class Inner {
private int f;
}
void go() {
Inner in = new Inner();
int value = in.f;
}
}
go
方法(在 java-11 之前)的字節碼調用已知的合成方法:
static int access$000(nestmates.Sample1$Inner);
descriptor: (Lnestmates/Sample1$Inner;)I
flags: ACC_STATIC, ACC_SYNTHETIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: getfield #1 // Field f:I
4: ireturn
從:
0: new #2 // class nestmates/Sample1$Inner
3: dup
4: aload_0
5: invokespecial #3 // Method nestmates/Sample1$Inner."<init>":(Lnestmates/Sample1;)V
8: astore_1
9: aload_1
10: invokestatic #4 // Method nestmates/Sample1$Inner.access$000:(Lnestmates/Sample1$Inner;)I
我知道這件事已經有一段時間了,但我從來沒有質疑過自己為什么會這樣? 為什么 javac 首先必須這樣做,為什么不直接通過 static 方法直接getField
和這種間接尋址。 謝謝你。
原因是外部類和內部類編譯成不同的 class 文件,這意味着它們不能訪問彼此的私有成員。
生成合成方法是為了有效地擴大從私有到包私有的訪問,但它是通過不必要的間接方式實現的。
在 Java 11 中,他們為此引入了一個新概念,即允許不同文件中的類在某些情況下(即本例)訪問彼此的私有成員。
參見 JEP: https://openjdk.org/jeps/181
這是一個基於許多因素的簡單結論:
在 JVM(類文件java.exe
)級別,內部類根本不存在。 完全沒有。 Javac 通過命名您的Inner
class Sample1 Sample1$Inner
來“偽造”它(那個美元不是渲染,那是它的實際 JVM 級別名稱, $ 只是一個符號,與I
或n
一樣有效),添加一個參數類型Sample1
作為 Inner 的所有構造函數的第一個參數,將new Sample1()
替換為new Sample1(this)
,將instanceOfOuter.new Sample1()
替換為new Sample1(instanceOfOuter)
,具有Sample1
類型的final
字段(這些構造函數設置通過使用第一個參數),將所有對外部方法的調用轉換為在該字段上調用(假設在 JVM 級別沒有“外部 this”,因為沒有外部類),等等。 您可以使用javap -c
查看所有這些。
private
成員不能被除自身之外的任何類型訪問,因此,假設內部 class 不再是編譯結束后的那個(因為 JVM 首先沒有內部 class 概念),它需要一個包 -私有(或受保護或公共,如您所願)的方式來獲取它。
JVM也不知道合成意味着什么。 這是一個標志,是的,JVM 完全忽略了。 它不知道它意味着什么,它根本不影響代碼的運行或解釋方式。 是javac
知道合成的意思。 即:標記任何你編寫的東西,將精心管理的偽造物粘合在一起,使內部類看起來像是純粹的 java 語言(即編譯器)功能並且不存在於 java 運行時級別- 並且在閱讀 class 文件時,對它們視而不見。 就像這些合成方法不存在一樣。 比如,如果 javac 被要求編譯試圖調用合成方法的代碼,那么就像編譯試圖調用不存在的方法的代碼一樣。
正如評論已經指出的那樣,java 的更新版本確實在 JVM 級別引入了內部類的概念,但並不是通過在 JVM 規范中體現“內部類”的概念,而是通過具有“同胞”概念,這讓 class 文件列出其他 class 名稱,這些名稱可以“查看”並調用/與私有元素交互。 現代 javacs,如果以現代 JVM 為目標(這里的“現代”定義為:“具有可用的 nestmates 功能”),將使用 nestmates 並放棄所有合成。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.