當一個 private 內部類在外部類方法中被實例化時,javac 會悄悄生成一個**包可見的合成建構子**與一個**空的合成類**,這是為了繞過 JVM 存取驗證而進行的編譯器妥協。本文通過反編譯、位元組碼分析、JLS 規範解讀,深度揭示這一隱藏機制的原理、生成規則、安全影響與現代 Java 版本的演進。
Outer$1.class
public class Outer {
private class Inner {} // 私有內部類
private void createInner() {
new Inner(); // 在外部類私有方法中實例化
}
public static void main(String[] args) {
new Outer().createInner();
}
}
編譯後生成三個類檔案:
Outer.classOuter$Inner.classOuter$1.class ← **合成類,無任何欄位/方法**使用 jad / CFR / Fernflower 反編譯 Outer.class:
public class Outer {
private class Inner {
private Inner() {} // 原始私有建構子
// 合成建構子:包可見,接受合成類參數
Inner(Outer$1 x) { this(); }
}
public Outer() {}
private void createInner() {
Inner inner = new Inner(null); // 傳遞 null 作為合成類實例
}
public static void main(String[] args) {
new Outer().createInner();
}
}
// 合成類:完全空殼
class Outer$1 {}
private 建構子:僅宣告該建構子的類 可調用 (invokespecial)。Outer$Inner 的 private Inner() 僅允許 Outer$Inner 內部調用。Outer.createInner() 屬於 Outer,**無權直接調用** Outer$Inner 的私有建構子。Outer$Inner 生成**包可見 (default/package-private)** 合成建構子:Inner(Outer$1)。private Inner() (this())。Outer$1** 作為參數類型,區別於無參建構子。new Inner(null),傳遞 null (類型為 Outer$1),觸發合成建構子。Outer$1?Inner()),將與原始私有建構子**簽名衝突** (同名同參)。Outer$1),實現**方法多載區分**。Outer$1 為包可見空類,**零運行時開銷**,僅作為編譯期類型標記。
class A { class B { class C { private C() {} } } }
每一層 private 內部類在外層實例化時,都會生成對應的合成建構子與合成類 (A$B$1, A$B$C$1...)。
new Runnable() { private void internal() {} }; // 匿名類本身可有私有成員
匿名類本質是非靜態內部類,同樣遵循合成機制。但匿名類通常不會在外部手動實例化 (由 new 表達式直接創建),故較少觸發此現象。
invokedynamic + LambdaMetafactory 運行時生成實現類。private 合成類,**無合成建構子機制** (由 LambdaMetafactory 直接調用私有建構子,JVM 允許 invokedynamic bootstrap 方法繞過存取檢查)。new Outer.Inner(new Outer$1())。Outer$1 為合成類,**源碼層面不可引用** (命名含 $+數字,非合法標識符)。Constructor.setAccessible(true)) 或**位元組碼操作** (ASM/ByteBuddy) 調用合成建構子,**繞過 private 限制**。setAccessible(true) (模組化系統 java --add-opens 限制)。NestHost/NestMembers 類檔屬性,**JVM 原生支援巢狀類私有成員互訪**,**無需合成建構子**!javac --release 11 (或更高) 編譯上述代碼,**不再生成 Outer$1.class 和合成建構子**。private Inner() 可直接被 Outer.createInner() 的 invokespecial 調用,JVM 驗證器檢查 NestHost 關係放行。
# Java 8 編譯
javac -source 1.8 -target 1.8 Outer.java
ls Outer*.class
# Outer.class Outer$1.class Outer$Inner.class
# Java 17 編譯 (預設 release 17)
javac Outer.java
ls Outer*.class
# Outer.class Outer$Inner.class (無 Outer$1.class!)
# 反編譯 Java 17 版 Outer$Inner.class
# 僅有 private Inner(),無合成建構子
Java 內部類私有建構子的合成機制,是**編譯器為適應 JVM 早期存取驗證限制而做出的巧妙妥協**。它揭示了語言特性、編譯器實現、JVM 驗證器、類檔案格式之間複雜的互動關係。**Java 11 引入巢狀類存取控制 (JEP 181) 從根源解決了此問題**,移除了合成建構子與合成類,實現了真正語義級的巢狀封裝。理解這一演進歷程,不僅能解開反編譯時的疑惑,更能加深對 Java 封裝模型、編譯器工程、JVM 規範演進的系統性認知。