空指針異常 (NPE) 是 Java 開發中最常見的運行時錯誤。Tony Hoare 稱其為「十億美元錯誤」。本文系統總結從傳統判空到現代語言特性的多層次防禦體系。
if (obj != null) { obj.doSomething(); } // 代碼冗長、易漏判、語義不清
| 方案 | 適用場景 | 優勢 | 版本 |
|---|---|---|---|
| Optional<T> | 返回值可能為空的方法 | 強制調用者處理空情況、鏈式調用、語義明確 | Java 8+ |
| Objects.requireNonNull | 方法參數/字段非空檢查 | 拋出帶訊息 NPE、編譯期無侵入、JDK 內建 | Java 7+ |
| 斷言 | 內部不變量檢查 | 可啟用/禁用、零性能開銷(生產關閉) | Java 1.4+ |
| @NonNull/@Nullable 註解 | 靜態分析、IDE 提示 | 編譯期預警、文檔化契約、工具鏈支援 | 需外部庫 |
// 創建
Optional opt = Optional.ofNullable(getName()); // 允許 null
Optional opt2 = Optional.of("value"); // 非 null,否則 NPE
Optional empty = Optional.empty();
// 消費
opt.ifPresent(System.out::println); // 有值才執行
String result = opt.orElse("default"); // 取值或默認
String result2 = opt.orElseGet(() -> expensiveDefault()); // 延遲默認
opt.orElseThrow(() -> new IllegalStateException("缺失")); // 異常
// 轉換
Optional len = opt.map(String::length); // 函數式映射
Optional filtered = opt.filter(s -> s.length() > 5); // 過濾
public void setName(String name) {
this.name = Objects.requireNonNull(name, "姓名不能為空"); // 拋 NPE 含訊息
}
// 靜態導入更簡潔
import static java.util.Objects.requireNonNull;
requireNonNull(user, "用戶對象必須非空");
優勢:堆疊跟蹤直接定位到 requireNonNull 行,比手動 if-null-throw 更快定位。
// 斷言:啟用 -ea 運行
assert connection != null : "數據庫連接未初始化";
// 註解(需 SpotBugs/Error Prone/IDE 支援)
@NonNull public String getName() { return name; }
@Nullable public String getNickname() { return nickname; }
@NonNull 註解 + Objects.requireNonNull 參數檢查Optional 表達可選結果,避免 null 語義模糊@NotNull (Bean Validation)