Effective Java Item15 最小化類別與成員的可存取性
整理 Effective Java 書中 Item 15: Minimize the accessibility of classes and members 心得筆記
主旨
在 Java 中設計良好的元件,首要原則是資訊隱藏(information hiding),也稱為封裝(encapsulation)。簡單來說,就是把類別的內部細節封起來,讓外部只能透過定義好的 API 存取必要功能。這可以降低系統各部分之間的耦合,提升開發效率、維護性與模組重用性。
採取「最小存取原則」
一個簡單卻強大的準則是:讓每個類別與成員的可見範圍儘可能小。你應該使用最嚴格的存取修飾詞,只給真正需要的地方使用權限。
Java 四種存取層級由小到大為:
private:僅限宣告該成員的類別內使用- default(package-private):不加任何修飾詞,代表只能在同個 package 中使用
protected:子類別與同 package 類別可存取public:任何地方都可存取
對類別的建議
- 頂層類別只能是
public或 package-private(預設)。 - 若類別不是給外部使用的,就不要加
public,讓它留在 package 內部。 - 這樣一來未來你要重構、移除或改名這個類別,都不用擔心破壞其他使用者的程式。
若某類別只被一個類別使用,還可以進一步做成那個類別的 private static 嵌套類別,進一步隱藏實作細節。
對成員的建議
- 除了必要的 API 方法外,其他欄位與方法預設都設為
private - 只有當同 package 中其他類別需要存取時,才考慮使用 package-private
- 很少需要使用
protected,因為這會讓成員變成「公開 API」的一部分,未來不能輕易變動
覆寫(override)限制
覆寫父類別方法時,不能縮小可見範圍。例如父類是 public,子類的覆寫方法也必須是 public,否則編譯會出錯。這是為了遵守「里氏替換原則」(Liskov Substitution Principle)。
常見問題:為了測試開放存取
你可能會為了測試方便,想把 private 改成 public。實際上不需要。你可以讓測試類別與被測類別放在同一個 package,就能使用 package-private 層級進行測試。
請不要為了測試,把類別或欄位暴露成整個系統可見。
公開欄位的風險
public欄位幾乎永遠不該出現在public類別中- 若欄位是可變的,外部可以直接更改內容,會破壞封裝、無法追蹤狀態變化,也無法確保執行緒安全
- 即使欄位是
final或指向不可變物件,也會讓你日後難以更換資料結構
正確做法:
方式 1:提供不可變的 List 包裝:
方式 2:提供複製陣列的方法:
依據使用情境與效能考量來選擇哪種方式較適合。
Java 模組系統(Java 9+)
Java 9 引入模組系統,提供「跨 package 區隔」的額外可見層級。模組的概念像是更高層級的封裝單位,可以讓你指定哪些 package 要暴露給外部、哪些要保留內部使用。
但模組系統目前主要用於 JDK 本身,在一般應用程式開發中採用並不普遍,配置與使用也較為複雜。除非你有明確需求,否則建議先不使用模組系統。
真實世界範例
你維護一個開放 API 的 SDK,如果你貪圖方便把內部類別都設為 public,將來就算想移除也會被使用者卡住。若一開始就設為 package-private,未來只要 API 沒變,用戶端完全不受影響,重構起來輕鬆自在。
小結
封裝不是可有可無的設計細節,而是寫出好程式的基本功。只開放該公開的 API,其他一律鎖起來,才能保持系統彈性、避免未來負債。記住:
- 先設為
private,必要時才放寬權限 - 不要讓測試理由成為「全世界都能看到」的藉口
public static final只用來公開不可變常數,不要公開可變結構