Effective Java Item 55:謹慎使用 Optional 作為回傳值
整理 Effective Java 書中 Item 55: Use Optional judiciously 心得筆記
主旨
在 Java 8 以前,當方法沒辦法回傳有效值時,我們只有兩個選擇:拋出例外 或 回傳 null。但這兩者都有明顯缺點。Java 8 引入的 Optional<T> 提供了更安全、更具表達力的選項,但這並不表示它應該無腦使用。
本篇會釐清什麼情況該用 Optional、什麼情況不該用,並提供實務建議。
點出問題:null 與例外都不完美
null的問題:- 呼叫端需額外寫 null 判斷,容易遺漏
- 容易導致晚發性
NullPointerException
- 拋出例外的問題:
- 應只用於真正異常的情況(Item 69)
- 創建例外物件會記錄堆疊,成本高
- 呼叫端需要寫 try-catch,降低可讀性
Optional 的出現
Optional<T> 是個容器,要嘛包著一個非 null 的 T,要嘛是空的。
用法很直觀,也提升了 API 的表達力:
相較於 null 或例外,Optional<T> 更能強迫呼叫者面對「可能沒有值」的狀況。
範例:取最大值
Java 8 stream 版本更簡潔:
如何處理 Optional?
- 有預設值:
.orElse(defaultValue) - 有預設邏輯:
.orElseGet(() -> computeDefault()) - 拋出自訂例外:
.orElseThrow(MyException::new) - 確定有值:
.get()(⚠️小心使用) - 函數式轉換:
.map(...)、.flatMap(...) - 判斷是否有值:
.isPresent()(通常可用 map/orElse 替代)
對照用法:
不該用 Optional 的情境
- ❌ 不要回傳
Optional<List<T>>、Optional<Stream<T>>- 用空集合即可(詳見 Item 54)
- ❌ 不要拿 Optional 當 Map 的 value
- 會導致「key 不存在」與「key 存在但值為空」兩種語義衝突
- ❌ 不要在效能敏感區域用
Optional<Integer>等 boxing 型別- 改用
OptionalInt,OptionalLong,OptionalDouble
- 改用
- ⚠️ 小心用 Optional 作為物件欄位
- 若欄位太多為 optional,可能代表應該要拆成子類別
- 但在需要表達「可能沒有值」的 primitive 欄位上可以考慮使用
Java 9 新增功能
Optional::stream():搭配flatMap處理Stream<Optional<T>>
ifPresentOrElse()、or()提供更多分支處理方式
小結
✅ 該使用 Optional 的情境:
- 方法 可能沒有值可以回傳
- 呼叫者 應該要處理沒有值的情況
- 不適合用 null,也不夠嚴重到拋出例外
⚠️ 不建議用 Optional 的情境:
- 容器型別(List、Map、Stream) → 回傳空集合
- 效能敏感程式區塊 → 避免 Boxing/Unboxing 的成本
- 非回傳值用途(欄位、集合內元素、Map 值)
Read other posts