JDK 原始碼中大量出現:
if (pathname == null) {
throw new NullPointerException();
}
疑問:既然呼叫端應自行檢查,為何 API 仍要重複檢查並拋出例外?這不是多此一舉嗎?
API 明確檢查前置條件,等同於在程式碼中「文件化」並「強制執行」契約。
| 策略 | 行為 | 後果 |
|---|---|---|
| 不檢查 | null 傳遞至底層 → 稍後在不相關位置拋出 NPE | 堆疊追蹤誤導、除錯困難、錯誤訊息模糊 |
| 主動檢查 | 入口即檢查 → 立即拋出具體例外 | 錯誤定位精準、訊息清晰、契約違反即時暴露 |
if (x == null) 指令級開銷可忽略// 呼叫端忘記檢查
File file = new File(null); // JDK 內部檢查攔截,拋出 NPE("pathname")
API 檢查成為最後防線,防止無效資料污染系統狀態。
// 呼叫端檢查有漏洞
if (path != null && path.length() > 0) { // 忘記 trim()
File file = new File(path); // 空白字串通過檢查,JDK 再次驗證
}
// 檢查時非 null,使用時已被其他執行緒設為 null
if (sharedObj != null) {
// Context switch 發生,sharedObj = null
sharedObj.doSomething(); // NPE 仍可能發生
}
API 內部檢查在實際使用瞬間驗證,縮小競態視窗。
// JDK 7 引入,標準化空值檢查
public void setPath(String path) {
this.path = Objects.requireNonNull(path, "path cannot be null");
}
// 更多實用方法
Objects.requireNonNullElse(obj, defaultVal); // 非空回傳 obj,否則 default
Objects.requireNonNullElseGet(obj, () -> default); // 延遲計算 default
Objects.checkIndex(index, length); // 索引邊界檢查
Objects.checkFromToIndex(from, to, length); // 範圍檢查
JDK API 的 null 檢查拋出 NullPointerException(未檢查),因為傳遞 null 給不接受 null 的參數屬於程式設計錯誤,不應強制呼叫端 try-catch,而應在開發/測試階段修正。
Objects.requireNonNull、IllegalArgumentException)@param、@throws、Nullable 註解(@NonNull、@Nullable)明確宣告契約JDK API 的主動檢查並非多餘,而是契約式設計與防禦性程式設計的體現。它在邊界處攔截錯誤、提供精準訊息、降低除錯成本,是建構健壯系統的關鍵基礎設施。開發者應理解其設計意圖,並在自有 API 中遵循相同原則。