public class Outer {
private String secret = "機密資料";
public class Inner {
public void printSecret() {
System.out.println(secret); // 編譯通過!直接存取外部 private 欄位
}
}
public static void main(String[] args) {
new Outer().new Inner().printSecret(); // 輸出: 機密資料
}
}
明明 private 定義為「僅限類別內部存取」,為何內部類能輕易跨越邊界?這是 Bug 嗎?
使用 javap -c Outer 反編譯觀察位元組碼:
Compiled from "Outer.java"
public class Outer {
// 編譯器自動生成的 package-private 靜態方法
static java.lang.String access$0(Outer);
Code:
0: aload_0
1: getfield #1 // Field secret:Ljava/lang/String;
4: areturn
public Outer();
public class Inner;
}
再看內部類 Outer$Inner:
public void printSecret();
Code:
0: aload_0
1: getfield #1 // Field this$0:LOuter; // 內部類持有外部實例參考
4: invokestatic #2 // Method Outer.access$0:(LOuter;)Ljava/lang/String;
7: ...
access$0(Outer) 等 合成方法,權限為 package-private(無修飾符)。this$0 持有外部實例參考。secret 實際呼叫 Outer.access$0(this$0)。結論:private 並未失效,而是編譯器在背後幫你建立了受控的存取管道。
public class AnotherOuter {
public static void main(String[] args) {
Inner inner = new AnotherOuter().new Inner();
System.out.println(inner.x); // 直接存取內部類 private 欄位
}
class Inner {
private int x = 10;
}
}
反編譯 AnotherOuter$Inner 同樣發現編譯器生成 static int access$0(Inner),外部類透過此方法讀取 x。
若成員或建構子宣告為 private,僅當存取發生於 包含該宣告的頂層類別主體內 時,才允許存取。(JLS §6.6.1)
「頂層類別」包含其巢狀類別,因此外部類與內部類互相存取 private 成員均合法。
使用 匿名內部類 並搭配介面型別:
public class PrivateGuard {
Runnable task = new Runnable() {
private int hidden = 42; // 真正封裝
@Override public void run() {
System.out.println(hidden);
}
};
public static void main(String[] args) {
PrivateGuard pg = new PrivateGuard();
// pg.task.hidden; // 編譯錯誤:Runnable 無此欄位
pg.task.run(); // 僅能透過介面方法間接觸發
}
}
因為 task 編譯時型別為 Runnable,外部無法取得匿名類別實際型別,自然無法存取其 private 成員。
| 特性 | Java 內部類 | C++ 巢狀類別 |
|---|---|---|
| 外部實例參考 | 隱含持有 (this$0) | 無隱含參考 |
| 存取外部 private | 允許(編譯器合成方法) | 需宣告 friend |
| 封裝邊界 | 頂層類別為單位 | 類別為單位 |
private 在 Java 中的封裝單位是 頂層類別,而非單一類別。private 成員是語言規格允許、編譯器自動實作的行為。