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

Java 訪問權限陷阱:protected 內部類跨包實例化編譯失敗深度解析

     編輯:Java 綜合教學

Java 訪問控制深度剖析:protected 內部類構造器可見性與跨包繼承陷阱

Java 四大訪問修飾符 (public、protected、default、private) 看似簡單,但結合**內部類**、**跨包繼承**、**構造器調用**時極易產生反直覺的編譯錯誤。本文通過一個經典案例,揭示 protected 內部類構造器的可見性規則。

案例代碼

// 父類包 pac1
package pac1;
public class Parent {
    protected int i;
    protected class Inner { } // protected 成員內部類
}

// 子類包 pac2
package pac2;
import pac1.Parent;

public class Child extends Parent {
    public static void main(String[] args) {
        new Child().i = 0;                    // ✅ 編譯通過
        Inner i = new Parent().new Inner();   // ❌ 編譯失敗
    }
}

錯誤信息

Error: Inner() has protected access in pac1.Parent.Inner

核心原因:構造器可見性 ≠ 類可見性

1. 類可見性檢查 (通過)

  • Parent.Inner 為 protected,子類 Child 繼承 Parent,**可見類類型**,故 Inner i 聲明合法。

2. 構造器可見性檢查 (失敗)

  • new Parent().new Inner() 調用的是 Inner 的**無參構造器**。
  • 內部類無顯式構造器時,**隱式構造器訪問級別與類一致**,即 protected。
  • 關鍵規則:protected 構造器**僅對同包類及子類可見**,但**子類必須通過繼承鏈訪問**,而非通過外部類實例訪問。
  • Child 雖繼承 Parent,但 new Parent() 創建的是 **父類實例**,而非子類實例。在 Parent 實例上調用 Inner() 視為**外部類代碼訪問**,非繼承訪問 → **不可見**。

JLS 規範依據 (§6.6.2.1)

A protected constructor can be accessed by a class in the same package, or by a subclass of its declaring class... **but only when the access occurs within the subclass itself** (i.e., the qualifying type of the constructor invocation is the subclass or a subclass thereof).

譯文:protected 構造器僅可在**子類自身代碼中**通過 super()、this() 或 new Child.Inner() (若 Inner 為 Child 成員) 訪問,**不能**通過父類實例 new Parent().new Inner() 訪問。

正確的三種解決方案

方案一:在子類中定義工廠方法 (推薦)

// Parent.java
package pac1;
public class Parent {
    protected class Inner { }
    // 提供 protected 工廠方法,子類可繼承調用
    protected Inner createInner() { return new Inner(); }
}

// Child.java
package pac2;
public class Child extends Parent {
    public static void main(String[] args) {
        Child child = new Child();
        Parent.Inner inner = child.createInner(); // ✅ 子類實例調用繼承的 protected 方法
    }
}

方案二:子類中實例化內部類 (需 Inner 非 private)

public class Child extends Parent {
    public static void main(String[] args) {
        Child child = new Child();
        Parent.Inner inner = child.new Inner(); // ✅ 在 Child 實例上創建 Inner,合法
        // 原理:Child 繼承了 Parent,故 Child 實例也是 Parent 實例,滿足外部類實例要求
    }
}

方案三:將 Inner 構造器提升為 public (若設計允許)

public class Parent {
    protected class Inner {
        public Inner() { } // 顯式 public 構造器
    }
}
// 此時 new Parent().new Inner() 合法,但破壞了封裝,慎用

避坑總結表

場景protected 類/構造器子類跨包訪問關鍵點
聲明變量類型類✅ 允許類型可見性按類級別檢查
new Outer().new Inner()構造器❌ 禁止外部類實例非子類實例,視為外部訪問
new Child().new Inner()構造器✅ 允許Child 實例滿足「在子類代碼中」+「合法外部實例」
super() / this()構造器✅ 允許構造器鏈調用屬於子類內部
繼承的 protected 方法創建方法✅ 允許方法在子類中調用,屬於子類代碼

最佳實踐建議

  1. 內部類默認 package-private:除非確需繼承體系共享,否則不要加 protected。
  2. 工廠方法模式:父類提供 protected Inner createInner(),子類復用/覆寫,封裝創建邏輯。
  3. 靜態內部類優先:無需外部實例綁定時,static class Inner 完全避開此問題。
  4. 編譯器是守門人**:信任編譯錯誤,它比直覺更符合 JLS 語義。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved