Java 內部類頻繁引用外部類變數,但這些變數如何傳遞給內部類?本文從字節碼層面揭示編譯器如何實現 final 語義與變數捕獲。
final int x = 10;
new Runnable() {
public void run() { System.out.println(x); }
}.run();
0: getstatic #2 // System.out 3: bipush 10 // 常量直接內聯 5: invokevirtual #3 // println 8: return
結論:基本型別 final 變數編譯期常量折疊,直接以字面值嵌入內部類字節碼,相當於值拷貝。
final T t = new T();
new Runnable() {
public void run() { System.out.println(t); }
}.run();
final T val$t 欄位(ACC_FINAL | ACC_SYNTHETIC)。T$1(T),外部傳入 t 賦值給 val$t。run() 中通過 getfield val$t 讀取。Thread thread = new Thread("thread-1") {
public void run() { System.out.println(t); }
};
編譯器將構造函數從 (String) 改為 (String, T),同步傳入外部捕獲變數。
int t = 3;
private void xx() {
new Runnable() {
public void run() { System.out.println(t); }
}.run();
}
內部類構造函數接收外部類 this 引用(this$0),運行時雙層 getfield 訪問最新值。不進行常量折疊,因為欄位可變。
final String t = new String("abc");
private void xx() { ... }
同樣傳遞 this$0,但在 run() 中走 getfield this$0 → getfield t 雙層訪問。
static String t = new String("abc");
this$0 引用(符合內部類規範)。run() 中直接 getstatic t 訪問,不再走實例欄位鏈。ACC_SYNTHETIC 欄位保存捕獲值或外部引用。final 局部變數直接常量內聯,避免欄位開銷。所有邏輯均在 編譯期 完成,JVM 僅見普通類與構造函數參數。SYNTHETIC 標記供反射識別編譯器生成成員(Member.isSynthetic())。
理解底層實現有助於: