在另一個類別內部定義的類別。核心優勢:**內部類實例天然持有外部類實例的引用,可直接訪問外部類所有成員(含 private)**,無需 getter/setter。
| 類型 | 定義位置 | 訪問外部成員 | 創建方式 | 典型場景 |
|---|---|---|---|---|
| 成員內部類 | 類內部、方法外 | 全部(含 private) | new Outer().new Inner() | 邏輯強綁定、迭代器實現 |
| 靜態內部類 | 類內部、static 修飾 | 僅 static 成員 | new Outer.StaticInner() | 單例模式、Builder、工具類 |
| 局部內部類 | 方法/代碼塊內部 | final/實際上 final 區域變數 + 外部成員 | 方法內 new LocalInner() | 臨時邏輯封裝、線程任務 |
| 匿名內部類 | 表達式中現寫現用 | 同局部內部類 | new Interface() { ... } | 事件監聽、策略模式、一次性實現 |
public class Outer {
private String outerField = "外部欄位";
private static String staticField = "靜態欄位";
// 成員內部類
public class Inner {
private String innerField = "內部欄位";
public void accessOuter() {
// 直接訪問外部實例欄位
System.out.println(outerField);
System.out.println(staticField);
// 訪問外部同名欄位:Outer.this.field
String outerField = "區域變數遮蔽";
System.out.println(outerField); // 區域變數
System.out.println(this.outerField); // 編譯錯誤:內部類無此欄位
System.out.println(Outer.this.outerField); // 正確:外部實例欄位
}
}
public void demo() {
Inner inner = new Inner(); // 隱含 this 參考
inner.accessOuter();
}
}
// 外部創建
Outer outer = new Outer();
Outer.Inner inner = outer.new Inner(); // 必須先有外部實例
public class Outer {
private static String staticField = "靜態欄位";
private String instanceField = "實例欄位";
public static class StaticInner {
public void access() {
System.out.println(staticField); // ✅ 可訪問
// System.out.println(instanceField); // ❌ 編譯錯誤:無外部實例引用
}
}
}
// 創建無需外部實例
Outer.StaticInner si = new Outer.StaticInner();
經典應用:線程安全單例模式
public class Singleton {
private Singleton() {}
private static class Holder {
static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return Holder.INSTANCE; // 類載入時初始化,天然線程安全
}
}
public class Outer {
private String field = "外部欄位";
public void method(final String param) { // Java 8+ 可省略 final
String localVar = "區域變數";
class LocalInner {
public void print() {
System.out.println(field); // 外部實例欄位
System.out.println(param); // 參數(final/實際上 final)
System.out.println(localVar); // 區域變數(final/實際上 final)
}
}
new LocalInner().print();
}
}
關鍵規則:只能訪問 final 或「實際上 final」(Java 8+ 不需顯式聲明)的區域變數/參數。原因:內部類實例可能比方法調用存活更久,需複製變數值而非引用。
// 實現介面
Runnable task = new Runnable() {
@Override
public void run() {
System.out.println("匿名類執行");
}
};
// 繼承類別
List list = new ArrayList<>() {
@Override
public boolean add(String s) {
System.out.println("攔截添加: " + s);
return super.add(s);
}
};
Outer.classOuter$Inner.class(成員內部類)Outer$1.class、Outer$2.class(匿名類,按出現順序編號)Outer$1Local.class(局部內部類,JDK 內部命名)| 場景 | 建議 | 理由 |
|---|---|---|
| 邏輯可複用、獨立性強 | 提升為獨立頂層類 | 降低耦合、提高可測試性 |
| 僅服務外部類、邏輯緊密 | 用 private 成員內部類 | 封裝性最好、外部不可直接實例化 |
| 不需訪問外部實例狀態 | 優先用 static 內部類 | 無外部引用、記憶體效率高、防內存洩漏 |
| 單一方法實現 | 用 Lambda 替代匿名類 | 代碼更簡潔、效能略優 |
| 序列化 | 謹慎實現 Serializable | 外部類也需可序列化,否則反序列化失敗 |
// 危險:非 static 內部類實例持有外部類引用
public class Activity {
private Inner inner = new Inner(); // 內部類實例
class Inner {
void doSomething() {}
}
}
// 如果 Inner 實例被長期持有(如靜態集合、單例、線程),
// 會導致 Activity 無法被 GC,洩漏其所有欄位(含 Context、View 等大對象)
// 修正:改為 static 內部類 + WeakReference
static class SafeInner {
WeakReference ref;
SafeInner(Activity a) { ref = new WeakReference<>(a); }
}
內部類是 Java 封裝與邏輯聚合的利器。**首選 static 內部類**,除非確實需要訪問外部實例狀態。匿名類在 Lambda 普及後地位下降,但多方法介面、需覆寫多方法、需存取外部狀態時仍不可替代。理解編譯產物、this 引用、final 限制、序列化陷阱,是進階 Java 開發者的必修課。