Java 允許在類內部定義類(巢狀類),分為 靜態內部類(static class)與 非靜態內部類(內部類/Internal Class)。二者在外部實例引用、訪問權限、實例化方式、序列化、記憶體洩漏風險上有本質區別。搞清這些,是寫出高內聚、低耦合、無洩漏代碼的關鍵。
| 維度 | 靜態內部類 (static class) | 非靜態內部類 |
|---|---|---|
| 外部實例引用 | 無(不持有 Outer.this) | 隱式持有 外部類實例引用 |
| 訪問外部成員 | 僅能訪問 static 成員 | 可訪問所有成員(含 private) |
| 實例化語法 | new Outer.StaticInner() | new Outer().new Inner() 或 outer.new Inner() |
| 獨立性 | 獨立於外部實例,可單獨存在 | 依附於外部實例,無外部實例無法創建 |
| 序列化 | 正常序列化 | 序列化時連帶序列化外部實例,極易導致 NotSerializableException |
| 記憶體洩漏風險 | 低 | 高(長生命周期內部類實例拖住外部實例及其所有字段) |
| 典型用途 | 工具類、Builder、單例持有者、緩存條目 | 迭代器、事件監聽器、需頻繁訪問外部狀態的行為類 |
public class OuterClass {
private static String staticField = "靜態字段";
private String instanceField = "實例字段";
// === 靜態內部類 ===
public static class StaticNested {
void access() {
System.out.println(staticField); // ✅ 可訪問靜態字段
// System.out.println(instanceField); // ❌ 編譯錯誤:無法訪問非靜態字段
}
}
// === 非靜態內部類 ===
public class Inner {
void access() {
System.out.println(staticField); // ✅
System.out.println(instanceField); // ✅ 可直接訪問外部實例字段
System.out.println(OuterClass.this.instanceField); // 等價寫法
}
}
// === 實例化示範 ===
public static void main(String[] args) {
// 1. 靜態內部類:無需外部實例
OuterClass.StaticNested sn = new OuterClass.StaticNested();
sn.access();
// 2. 非靜態內部類:必須有外部實例
OuterClass outer = new OuterClass();
OuterClass.Inner inner = outer.new Inner(); // 語法糖:outer.new Inner()
inner.access();
// 3. 靜態方法中創建非靜態內部類(需先創建外部實例)
// new OuterClass().new Inner(); // 一行寫法
}
}
public class LeakyActivity extends Activity {
// 非靜態內部類,隱式持有 LeakyActivity 實例
private class LeakyHandler extends Handler {
@Override public void handleMessage(Message msg) { /* ... */ }
}
private LeakyHandler handler = new LeakyHandler();
@Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 發送延遲消息,10分鐘後執行
handler.sendMessageDelayed(Message.obtain(), 10 * 60 * 1000);
// 若用戶在10分鐘內關閉 Activity,handler 仍持有 Activity 引用
// 導致 Activity 無法 GC,洩漏整個視圖層級、Bitmap 等大對象
}
}
public class SafeActivity extends Activity {
// 靜態內部類,不持有外部實例
private static class SafeHandler extends Handler {
private final WeakReference activityRef;
SafeHandler(Activity activity) {
this.activityRef = new WeakReference<>(activity);
}
@Override public void handleMessage(Message msg) {
Activity activity = activityRef.get();
if (activity != null) {
// 安全操作 activity
}
}
}
private final SafeHandler handler = new SafeHandler(this);
@Override protected void onDestroy() {
super.onDestroy();
handler.removeCallbacksAndMessages(null); // 及時清理
}
}
public class HttpRequest {
private final String url;
private final String method;
private final Map headers;
private final String body;
private final int timeout;
private HttpRequest(Builder builder) {
this.url = builder.url;
this.method = builder.method;
this.headers = builder.headers;
this.body = builder.body;
this.timeout = builder.timeout;
}
// 靜態內部類:無需外部實例,構建過程獨立
public static class Builder {
private String url;
private String method = "GET";
private Map headers = new HashMap<>();
private String body;
private int timeout = 5000;
public Builder url(String url) { this.url = url; return this; }
public Builder method(String method) { this.method = method; return this; }
public Builder header(String k, String v) { headers.put(k, v); return this; }
public Builder body(String body) { this.body = body; return this; }
public Builder timeout(int ms) { this.timeout = ms; return this; }
public HttpRequest build() {
if (url == null) throw new IllegalStateException("url required");
return new HttpRequest(this);
}
}
}
// 使用:鏈式調用,無外部實例依賴
HttpRequest req = new HttpRequest.Builder()
.url("https://api.example.com")
.method("POST")
.header("Content-Type", "application/json")
.body("{\"key\":\"value\"}")
.build();
public class Singleton {
private Singleton() {}
// 靜態內部類:類加載時不初始化,首次調用 getInstance() 觸發類初始化
private static class Holder {
static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return Holder.INSTANCE; // JVM 保證類初始化線程安全
}
}
new Runnable() {} 在非靜態方法中創建,隱式持有外部實例。長生命周期任務必須用 static 內部類或 Lambda(無捕獲外部變量時)。() -> outerMethod() 會捕獲 this。若 Lambda 存活時間長於外部實例,同樣洩漏。Serializable 會嘗試序列化外部實例。解決:改 static、或外部類也實現 Serializable、或用 transient 標記外部引用(需自定義 writeObject/readObject)。constructor.newInstance(outerInstance)。Outer.this、可訪問所有外部成員、實例化語法特殊、高洩漏風險、慎用。掌握這層本質,你的 Java 代碼將在記憶體安全與設計優雅上雙贏。