Effective Java Item 52:謹慎使用 overloading
整理 Effective Java 書中 Item 52: Use overloading judiciously 心得筆記
主旨
在 Java 中,方法的 overloading(多載) 是一種常見語法糖,但如果使用不當,容易導致令人困惑的行為與難以追蹤的錯誤。尤其當泛型、自動裝箱(autoboxing)、lambda 及 method reference 進入語言後,混淆性更高。本條目要提醒我們:能用不代表該用,overloading 要非常小心設計。
點出問題:overloading 不是動態綁定
先看一段看似沒問題的程式碼:
你可能以為會輸出:
Set
List
Unknown Collection
但實際輸出卻是:
Unknown Collection
Unknown Collection
Unknown Collection
原因在於 overloading 是編譯期決定的行為,而不是像 overriding 那樣在執行時依照物件實際型別動態 dispatch。這裡 c 是 Collection<?>,所以會選到第三個 classify(Collection<?>),即使實際上是 Set 或 List。
範例:Set 和 List 的 remove 陷阱
以下這段程式碼也充滿陷阱:
輸出為:
[-3, -2, -1] [-2, 0, 2]
set.remove(i) 呼叫的是 remove(Object),而 list.remove(i) 呼叫的是 remove(int)(移除第幾個 index),這就是 overloading 帶來的混淆。
修正方式:
明確告訴編譯器你要的是 remove(Object) 這個版本。
劃重點
- overloading 是靜態決定的,無法依據執行時型別做選擇。
- 避免讓多個 overload 有相同參數數量,尤其當它們型別容易混淆時(例如 Integer vs int)。
- 若參數型別無法「徹底區分」,就容易選錯版本。
- lambda 與 method reference 也會因為參數不明確導致編譯錯誤。
- 建議改用不同名稱的方法(像是
writeInt()vswriteLong()),比起 overloading 更清楚易懂。
真實世界範例
Java 標準庫的 String.valueOf(char[]) 和 String.valueOf(Object),傳入一個 char[] 陣列時會走不同邏輯,如果你不清楚哪個方法會被選中,結果可能出乎意料。
另一個例子是:
因為 submit 同時有 Runnable 和 Callable<T> 的 overloading,會讓 method reference 無法決定適用哪個。
小結
建議做法如下:
- 避免暴露多個參數數量相同的 overloading。
- 不同方法請使用不同名稱,而非倚賴 overloading。
- 若一定要 overloading,要確保參數型別彼此「截然不同」。
- lambdas 與 method references 特別容易踩雷,避免在同樣參數位置 overloading 不同 functional interface。
- 建構子沒得改名字,可考慮改用 static factory。
Overloading 本身不是錯,但錯在容易造成「以為會跑某個方法,實際卻跑了別的」。為了 API 可預測性與可維護性,慎用 overloading。