跳至內容

防止 NullPointerException

75% developed
來自華夏公益教科書,開放書籍,開放世界

導航 異常 主題:v  d  e )


NullPointerException 是一個 RuntimeException。在 Java 中,一個特殊的 null 可以被分配給一個物件引用。當應用程式嘗試使用物件引用時,NullPointerException 會被丟擲,該物件引用具有 null 值。這些包括

  • 在 null 引用所引用的物件上呼叫例項方法。
  • 訪問或修改 null 引用所引用的物件的例項欄位。
  • 如果引用型別是陣列型別,則獲取 null 引用的長度。
  • 如果引用型別是陣列型別,則訪問或修改 null 引用的槽。
  • 如果引用型別是 Throwable 的子型別,則丟擲 null 引用。

應用程式應丟擲此類的例項來指示對 null 物件的其他非法使用。

Example 程式碼部分 6.13:空指標。
Object obj = null;
obj.toString();  // This statement will throw a NullPointerException

上面的程式碼展示了 Java 的一個陷阱,也是最常見的錯誤來源。沒有建立物件,編譯器也無法檢測到它。NullPointerException 是 Java 中最常見的異常之一。

為什麼我們需要 null?

[edit | edit source]

我們需要它的原因是,很多時候,我們需要在物件本身建立之前建立物件引用。物件引用不能沒有值而存在,所以我們為它分配 null 值。

Example 程式碼部分 6.14:未例項化的宣告物件。
public Person getPerson(boolean isWoman) {
  Person person = null;
  if (isWoman) {
    person = createWoman();
  } else {
    person = createMan();
  }
  return person;
}

程式碼部分 6.14 中,我們希望在 if-else 中建立 Person,但我們也希望將物件引用返回給呼叫者,所以我們需要在 if-else 外部建立物件引用,因為 Java 中的 作用域規則。錯誤的錯誤處理和糟糕的契約設計可能是任何程式語言的陷阱。這對 Java 來說也是如此。

現在我們將描述如何防止 NullPointerException。它沒有描述你應該如何編寫 Java 的一般技術。這有點用處,可以讓你更加了解 null 值,並更加小心地自己生成它們。

這個列表並不完整——沒有規則可以完全防止 Java 中的 NullPointerException,因為標準庫必須使用,它們會導致 NullPointerException。此外,可以觀察到 Java 中未初始化的 final 欄位,因此你甚至不能在物件建立期間完全信任 final 欄位。

一個好的方法是先學習如何處理 NullPointerException,並精通它。這些建議將幫助你減少 NullPointerException 的出現,但它們不能替代你需要了解 NullPointerException 的需要。

將字串變數與字串字面量進行比較

[edit | edit source]

當您將變數與字串字面量進行比較時,大多數人會以這種方式進行

Example 程式碼部分 6.15:錯誤的比較。
if (state.equals("OK")) {
  ...
}

始終將字串字面量放在首位

Example 程式碼部分 6.16:更好的比較。
if ("OK".equals(state)) {
  ...
}

如果 state 變數為 null,則第一個示例中會得到 NullPointerException,而第二個示例中則不會。

最大限度地減少在賦值語句中使用關鍵字 'null'

[edit | edit source]

這意味著不要做以下事情

Example 程式碼部分 6.17:宣告異常。
String s = null;
while (something) {
    if (something2) {
        s = "yep";
    }
}

if (s != null) {
    something3(s);
}

您可以用以下程式碼替換它

Example 程式碼部分 6.18:宣告異常。
boolean done = false;

while (!done && something) {
    if (something2) {
       done = true;
       something3("yep");
    }
}

您也可以考慮在第一個示例中用 "" 替換 null,但預設值會導致由於預設值遺留而產生的錯誤。實際上,NullPointerException 更好,因為它允許執行時通知您錯誤,而不是隻使用預設值繼續執行。

最大限度地減少使用 new Type[int] 語法建立物件陣列

[edit | edit source]

使用 new Object[10] 建立的陣列有 10 個 null 指標。這比我們想要的要多 10 個,所以使用集合代替,或者在初始化時用以下程式碼顯式填充陣列

Example 程式碼部分 6.19:宣告異常。
Object[] objects = {"blah", 5, new File("/usr/bin")};

Example 程式碼部分 6.20:宣告異常。
Object[] objects;
objects = new Object[]{"blah", 5, new File("/usr/bin")};

檢查從 '不可信' 方法獲取的所有引用

[edit | edit source]

許多可以返回引用的方法可以返回 null 引用。確保您檢查這些方法。例如

Example 程式碼部分 6.21:宣告異常。
File file = new File("/etc");
File[] files = file.listFiles();
if (files != null) {
    stuff
}

如果 /etc 不是目錄,則 File.listFiles() 可能返回 null。

如果您願意,您可以決定信任某些方法不會返回 null,但這是一種假設。一些沒有指定它們可能返回 null 的方法實際上返回了 null,而不是丟擲異常。

foreach 迴圈陷阱

[edit | edit source]

如果您在 foreach 迴圈中迴圈陣列或集合,請小心。

Example 程式碼部分 6.22:訪問集合。
Collection<Integer> myNumbers = buildNumbers();
for (Integer myNumber : myNumbers) {
  System.out.println(myNumber);
}

如果物件為空,它不會只執行零次迴圈,而是會丟擲空指標異常。所以不要忘記這種情況。新增一個 `if` 語句或返回空集合。

Example 程式碼部分 6.23:訪問集合的安全性。
Collection<Integer> myNumbers = buildNumbers();
if (myNumbers != null) {
  for (Integer myNumber : myNumbers) {
    System.out.println(myNumber);
  }
}

外部工具

[編輯 | 編輯原始碼]

有一些工具,比如 FindBugs,可以解析你的程式碼並警告你潛在的錯誤。大多數情況下,它可以檢測可能的空指標。


Clipboard

待辦事項
新增一些類似於變數中的練習


華夏公益教科書