整理 Effective Java 書中 Item 64: Refer to objects by their interfaces 心得筆記

主旨

當在宣告變數、參數或方法回傳值時,應該盡可能使用介面(interface)作為型別,而不是具體類別(class)。

這個做法讓程式碼更具彈性、更容易維護,也符合「針對抽象程式設計」的物件導向原則。

範例:用介面取代具體類別

// 建議做法:用 Set 當作型別
Set<Son> sonSet = new LinkedHashSet<>();

而不是這樣:

// 不建議:用實作類別當型別
LinkedHashSet<Son> sonSet = new LinkedHashSet<>();

這麼做有什麼好處?

  • 未來如果要改用 HashSetTreeSet,只要改建構子部分即可:

    Set<Son> sonSet = new HashSet<>();
    
  • 呼叫端不依賴具體實作,因此變動的範圍小、不容易出錯。


劃重點:為什麼這樣比較好?

比較項目用介面(Set)用類別(LinkedHashSet)
替換實作方便✅ 只需改建構子❌ 宣告也要改
相依性低、耦合低❌ 容易被綁死
支援多型、測試彈性❌ 不好 mock 或替換
風格更一致、簡潔❌ 看起來笨重、易出錯

注意事項與例外情況

何時可以用類別當型別?

  1. 沒有對應介面的值物件
    StringBigDecimalBigInteger 本來就是值物件,用類別沒問題。

  2. 框架原生就是以類別為核心的
    例如 Java IO 的 OutputStreamInputStream,用抽象類別來當型別是合理的。

  3. 你真的需要使用某個實作類別的額外方法
    比如你用 PriorityQueue 是因為它有 comparator() 這個方法,但 Queue 介面沒有。這種情況才合理用類別型別。


真實世界範例

假設你寫了一個訂單系統,裡面有以下寫法:

ArrayList<Order> orders = new ArrayList<>();

這樣未來如果想改成 LinkedListCopyOnWriteArrayList 就會比較麻煩。建議改為:

List<Order> orders = new ArrayList<>();

這樣在需要時可以輕鬆換實作類別,而不動到其他邏輯。


小結:最佳實務速查表

情境建議型別宣告
一般集合、清單、對映List / Set / Map 介面
資料來源是使用者輸入或程式邏輯決定的實作用介面
沒有對應介面的值物件(如 String用類別
需使用特定實作才有的方法用實作類別(但盡量避免)
測試中想 mock 或注入不同實作用介面,更好切換實作

結論:除了建立物件時幾乎都應該用「介面作為型別」,讓你的程式更彈性、可測試又好維護!