在 Java 程式設計師遇到的所有異常中,空指針異常 最為恐怖——它提供的資訊最少,不指明預期型別,也不指出空值賦值位置。許多開發者表示絕大多數崩潰源於此,渴望靜態識別工具。雖然理論證明無法完全靜態判定所有空指針,但透過工具排除大多數、結合設計技巧顯著減少發生,已是可行之路。本文探討第一類常見錯誤模式:懸空複合。
此錯誤源於遞歸資料型別的基礎實例未定義專屬類別,改以空指針填充複合資料結構,客戶端代碼卻視同正確填充。稱為「懸空複合」是因複合設計型別包含懸空引用(即空指針)。
public class LinkedList {
private Object first;
private LinkedList rest;
public LinkedList() { this.first = null; this.rest = null; } // 空鏈表
public LinkedList(Object _first) { this.first = _first; this.rest = null; } // 錯誤:應為 new LinkedList()
public LinkedList(Object _first, LinkedList _rest) { this.first = _first; this.rest = _rest; }
}
此處用兩個 null 表示空鏈表,而非獨立類別。單參數建構子錯誤地將 rest 設為 null 而非空鏈表實例。這種「空指針與基礎實例佔位符混淆」極易導致錯誤。
public boolean equals(Object that) {
if (this.getClass() == that.getClass()) {
LinkedList _that = (LinkedList)that;
if (this.isEmpty() || _that.isEmpty()) {
return this.isEmpty() && _that.isEmpty();
} else {
return this.getFirst().equals(_that.getFirst()) &&
this.getRest().equals(_that.getRest()); // 遞歸調用若遇空指針即拋出 NPE
}
}
return false;
}
若鏈表包含單參數建構子建立的節點,遞歸最終會在應為空鏈表處遇到 null,調用 getFirst() 或 getRest() 即拋出空指針異常。
採用類別層次結構明確區分空與非空節點:
List 介面定義共同行為Empty 類別實作空鏈表Cons 類別實作非空節點LinkedList 作為可變容器封裝不可變內部結構interface List {
Object getFirst();
List getRest();
}
class Empty implements List {
public Object getFirst() { throw new NoSuchElementException(); }
public List getRest() { throw new NoSuchElementException(); }
public boolean equals(Object that) { return this.getClass() == that.getClass(); }
}
class Cons implements List {
Object first; List rest;
Cons(Object _first) { this.first = _first; this.rest = new Empty(); } // 明確建立空實例
Cons(Object _first, List _rest) { this.first = _first; this.rest = _rest; }
// ... getFirst, getRest, equals 實作簡潔且型別安全
}
public class LinkedList {
private List value;
public LinkedList() { this.value = new Empty(); }
public LinkedList(Object _first) { this.value = new Cons(_first); }
public LinkedList(Object _first, LinkedList _rest) { this.value = new Cons(_first, _rest.value); }
// 委派給內部 value,邏輯清晰,錯誤在編譯期即可發現
}
if-else 鏈Empty)編譯期即可捕捉NoSuchElementException 取代無意義的空指針異常| 類型 | 懸空複合 |
|---|---|
| 症狀 | 使用遞歸定義資料型別的代碼拋出空指針異常 |
| 原因 | 基礎實例無專屬類別,空指針混入複合結構,客戶端處理不一致 |
| 解決/預防 | 確保基礎實例表示與檢查一致性,為每個基礎實例定義專屬類別 |
下一期將探討另一普遍且被證明屬於空指針異常的錯誤模式,以及識別與避免策略。