首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java private 修飾符「失效」真相:內部類存取機制與編譯器祕辛

     編輯:Java 相關知識

現象:private 似乎「失效」了?

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: ...

關鍵發現

  1. 編譯器在外部類生成 access$0(Outer) 等 合成方法,權限為 package-private(無修飾符)。
  2. 內部類透過隱藏欄位 this$0 持有外部實例參考。
  3. 存取 secret 實際呼叫 Outer.access$0(this$0)。

結論:private 並未失效,而是編譯器在背後幫你建立了受控的存取管道。

另一種情況:外部類存取內部類 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。

Java 語言規範 (JLS) 依據

若成員或建構子宣告為 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 成員。

與 C++ 差異對照

特性Java 內部類C++ 巢狀類別
外部實例參考隱含持有 (this$0)無隱含參考
存取外部 private允許(編譯器合成方法)需宣告 friend
封裝邊界頂層類別為單位類別為單位

總結

  • private 在 Java 中的封裝單位是 頂層類別,而非單一類別。
  • 內部類與外部類互相存取 private 成員是語言規格允許、編譯器自動實作的行為。
  • 若需嚴格封裝,請使用 匿名內部類 + 介面型別 斷開編譯時型別關聯。
  • 理解此機制有助於正確設計封裝邊界,避免誤以為安全漏洞而過度防禦。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved