Effective Java Item24:比起非靜態成員類型,更偏好靜態成員類型
整理 Effective Java 書中 Item 24: Prefer static member classes to non-static member classes 心得筆記
主旨
在 Java 中,巢狀類別(Nested Class)是一種將類別定義在另一個類別內部的設計方式。根據是否需要外部類別的實例,有四種巢狀類別:static member class
、non-static member class
、local class
和 anonymous class
。本篇聚焦在:當巢狀類別不需要外部類別實例時,應優先使用 static member class,這樣可以節省記憶體、提升效能、避免記憶體洩漏,也對設計更有彈性。
點出問題:你可能不小心造成記憶體洩漏
很多人會寫出像這樣的內部類別:
這段看起來沒問題,但 Inner
是非靜態類別,代表它 會自動持有一個指向 Outer
實例的隱藏參考。只要 Inner
還在記憶體中,Outer
就無法被 GC 回收。
如果你根本不需要 Inner
使用 Outer
的資料,這樣的隱藏參考就等於白白浪費記憶體,甚至導致 記憶體洩漏。
範例:正確的 static member class 寫法
假設你寫一個map類別,裡面每組 key-value 對應的資料用一個 Entry
表示。這個 Entry
不需要知道整張 map 的狀態,就可以獨立運作。
這時應該這樣寫:
因為加了 static
,這個 Entry
就不會默默持有外部類別 MyMap
的參考。這樣的設計更有效率,也不會製造多餘的相依性。
劃重點:何時該用 static?何時不用?
類型 | 是否需 Outer instance | 適用情境 |
---|---|---|
static member class | 否 | 若內部類別能獨立運作,例如 Entry , Operation , Node 等 |
non-static member class | 是 | 需要訪問外部類別的狀態,例如 Iterator 要操作外部資料結構 |
local class | 否/是 | 僅在方法內短暫使用時 |
anonymous class | 否/是 | 一次性行為,如事件處理、函式物件 |
範例:快取資料結構設計
這裡設計一個簡單的快取類別 SimpleCache
,裡面用一個巢狀類別 CacheEntry
來表示每一筆快取資料。每筆資料有 value
和 expiresAt
,方便過期清除:
真實世界示範
假設我們要放入大量快取資料:
如果你把 CacheEntry
改成 非 static 類別,每一筆資料都會不小心帶著一份 SimpleCache
的參考。這表示:
- 原本早就不會再用到的
SimpleCache
物件,會因為某筆CacheEntry
被外部誤引用,而永遠無法回收。 - 這種記憶體洩漏很難發現,因為 reference 是「隱藏的」。
這就是為什麼只要你的內部類別不需要存取外部類別實例,就應該 果斷加上 static
。
小結
- 如果巢狀類別不需要存取外部類別的資料或方法,請一律使用
static
。 - 使用 non-static 會自動建立對外部實例的參考,可能會浪費記憶體,甚至導致 記憶體洩漏。
- 若該巢狀類別是 API 的一部分,一開始就設計成
static
更好,未來才能維持相容性。 - 巢狀類別的選擇不只是語法問題,而是設計品質與效能的關鍵。