Java 1.1 引入內部類與匿名類,不僅是語法糖,更是封裝粒度細化、回調模式原生化、事件驅動編程簡化的關鍵支撐。本文系統梳理其語法規則、訪問權限、實例化機制、常見陷阱,並通過實戰場景展示如何用好它們寫出更優雅、可維護的代碼。
| 類型 | 定義位置 | 訪問外部成員 | 實例化語法 | 典型場景 |
|---|---|---|---|---|
| 成員內部類 | 類內部、方法外部 | 所有(含 private) | outer.new Inner() | 強關聯實體(如 LinkedList.Node) |
| 靜態嵌套類 | 類內部、方法外部,有 static | 僅 static 成員 | new Outer.Nested() | 工具類、Builder、獨立邏輯單元 |
| 局部內部類 | 方法/代碼塊內部 | 外部所有 + 最終局部變量/參數 | 在方法內 new Local() | 臨時邏輯封裝、策略實現 |
| 匿名內部類 | 表達式中,無名稱 | 同局部內部類 | new Interface() { ... } | 事件監聽、回調、Runnable/Callable |
public class Outer {
private class PrivateInner {} // 僅 Outer 可見
protected class ProtectedInner {} // 同包 + 子類可見
class PackageInner {} // 同包可見
public class PublicInner {} // 任意處可見
// 靜態嵌套類同樣支持四種修飾符
private static class StaticNested {}
}
關鍵點:內部類本質是外部類的成員,權限語義與字段/方法完全一致。
// 完全限定名
Outer.Inner
Outer.StaticNested
Outer$1 (匿名類編譯後名稱)
// 導入
import com.example.Outer.Inner; // 導入特定內部類
import com.example.Outer.*; // 導入外部類所有靜態成員內部類(不含非靜態)
// 源碼
class Outer {
private int value = 42;
class Inner {
void print() { System.out.println(value); }
}
}
// 編譯器生成的實際構造函數
class Inner {
final Outer this$0; // 隱式引用
Inner(Outer outer) { this.this$0 = outer; }
void print() { System.out.println(this$0.value); }
}
Outer outer = new Outer();
// 在外部類實例方法中:隱式 this
Inner inner1 = new Inner(); // 實際:new Inner(this)
// 在外部類靜態方法/外部類外部:顯式提供外部實例
Outer.Inner inner2 = outer.new Inner(); // 必須語法
// 靜態嵌套類:無隱式引用,像普通類一樣
Outer.StaticNested nested = new Outer.StaticNested();
class Inner {
// static int count = 0; // 編譯錯誤
static final int CONST = 42; // 允許:編譯時常量
// static void init() {} // 編譯錯誤
}
// 實現接口
Runnable task = new Runnable() {
@Override public void run() { System.out.println("Running..."); }
};
// 擴展類(含抽象類)
WindowAdapter adapter = new WindowAdapter() {
@Override public void windowClosing(WindowEvent e) { onClose(); }
};
// 帶構造函數參數擴展類
List list = new ArrayList(100) {
@Override public boolean add(String s) { return super.add(s.toUpperCase()); }
};
for (int i = 0; i < 3; i++) {
Runnable r = new Runnable() { public void run() { System.out.println(i); }};
// 每次循環創建同一個匿名類的新實例,而非新類
// 共享同一個 Class 對象
}
void startTask(final String name) { // Java 7 需顯式 final
Runnable task = new Runnable() {
public void run() {
System.out.println(name); // 捕獲局部變量
}
};
new Thread(task).start();
}
// Java 8+:實際最終性即可,無需 final 關鍵字
void startTask(String name) { // name 不再修改
Runnable task = new Runnable() {
public void run() { System.out.println(name); }
};
}
原理:匿名類構造函數隱式接收捕獲的局部變量副本,要求變量不再變化以保證語義一致性。
// 優化前:獨立類污染包命名空間
class Node { Object data; Node next; }
class LinkedList { Node head; ... }
// 優化後:Node 隱藏在 LinkedList 內部
public class LinkedList {
private static class Node { // 無需訪問外部實例,用 static
Object data; Node next;
Node(Object data, Node next) { this.data = data; this.next = next; }
}
private Node head;
// 所有操作內部實現,外部不可直接構造 Node
}
收益:封裝性↑、命名空間污染↓、意圖明確(Node 僅服務於 LinkedList)
// 匿名類版:緊湊、就地、可訪問私有方法
JButton btn = new JButton("關閉");
btn.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
onClose(); // 直接調用私有方法
}
});
// 對比:獨立類版本
class CloseListener implements ActionListener {
private final JFrame frame;
CloseListener(JFrame frame) { this.frame = frame; }
public void actionPerformed(ActionEvent e) { frame.dispose(); }
}
// 需顯式傳遞 frame、單獨文件、調用私有方法需包訪問權限
public void process(Data data, boolean useCache) {
// 根據條件就地定義策略,避免創建多個獨立策略類
Processor strategy = useCache ? new Processor() {
public Result process(Data d) { return cache.getOrCompute(d); }
} : new Processor() {
public Result process(Data d) { return computeDirectly(d); }
};
strategy.process(data);
}
Serializable 時謹慎評估;優先用 static 嵌套類InterfaceA.super.method()對於單方法接口,Lambda 更簡潔:
// 匿名類
btn.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) { onClose(); }
});
// Lambda(函數式接口)
btn.addActionListener(e -> onClose());
// 方法引用
btn.addActionListener(this::onClose);
適用性對比:
| 場景 | 推薦方案 |
|---|---|
| 單方法接口、邏輯簡單 | Lambda / 方法引用 |
| 多方法接口/抽象類 | 匿名類 |
| 需訪問私有字段/方法、邏輯復雜 | 成員內部類 / 獨立類 |
| 需序列化、長生命周期 | static 嵌套類 / 獨立類 |
內部類與匿名類是 Java 面向對象建模能力的重要延伸:
掌握其機制、權限、生命周期、序列化特性,能寫出結構更清晰、封裝更嚴密、意圖更明確的 Java 代碼。記住:內部類是為了「隱藏」,匿名類是為了「就地」,Lambda 是為了「簡潔」——按需選擇,才是高手。