想避開NullPointerException?我給你10條建議

  1. <strong>
  2. <strong>
  3. <strong>
  4. <strong>
想避開NullPointerException?我給你10條建議

1. 引言

NullPointerException應該是 Java 開發中最常出現的問題,也是 Java 程序員最容易犯的錯誤。雖然看起來是個小錯誤,但帶來的影響卻不小,Tony Hoare(null 引用的發明者)在 2009 年說過 NPE 大約給企業造成數十億美元的損失。在這工作半年內,我就踩了好幾次 NPE 的坑。舉個例子,我需要在原有邏輯上加一段代碼,而新加的代碼報錯拋出了 NPE,同時又沒做異常處理,就直接導致後面的邏輯不運行了,影響了整個原有邏輯,太恐怖了。所以大家一定要小心避開 NPE 這個坑。

本文將會從以下兩個方面說起:

  • 發生 NPE 的可能情況
  • 避開 NPE 的建議

2. 發生 NPE 的可能情況

首先我們需要清楚 NPE 是怎麼發生的。

<code>String s;String[] ss;/<code>

當聲明一個引用變量時,若未指定其指向的內容,Java 會將其默認指向 null,一個空地址,意味著“什麼都沒有指向”。後續若也沒有為該變量賦值,則當使用這個變量裡的內容時,便會拋出 NPE。

例如通過.去訪問方法或者變量,[]去訪問數組插槽:

<code>System.out.println(s.length());System.out.println(ss[0]);/<code>

以下是 NPE 的 Javadoc 概述的 6 個可能發生情況

  1. 在空對象上調用實例方法。對空對象調用靜態方法或類方法時,不會報 NPE,因為靜態方法不需要實例來調用任何方法;
  2. 訪問或更改空對象上的任何變量或字段時
  3. 拋出異常時拋出 null
  4. 數組為 null 時,訪問數組長度
  5. 數組為 null 時,訪問或更改數組的插槽
  6. 對空對象進行同步或在同步塊內使用 null

3. 避開 NPE 的建議

這節將介紹如何在開發過程中避開 NPE 的一些建議。

(1)儘量避免在未知對象上調用 equals() 方法和 equalsIgnoreCase() 方法,而是在已知的字符串常量上調用

由於 equals() 和 equalsIgnoreCase() 具有對稱性,所以可以直接翻轉,這是很容易實現的。

<code>Object unknowObject = null;if (unknowObject.equals("knowObject")) {    System.out.println("如果 unknowObject 是 null,則會拋出 NPE");}if ("knowObject".equals(unknowObject)) {    System.out.println("避免 NPE");}/<code>

(2)避免使用 toString(),而是 String.valueOf()

這是因為 String.valueOf() 中做了非空校驗,同樣裡面也調用了對象的 toString()方法,所以結果是相同的。

<code>Object unknowObject = null;System.out.println(unknowObject.toString());System.out.println(String.valueOf(unknowObject));/<code>

(3)使用 null 安全的方法和庫

開源庫的方法通常都了非空校驗,例如 Apache common 庫中的 StringUtils 工具類中的 isBlank()、isNumeric() 等方法,使用時不必擔心 NPE。那我們在使用第三方庫時,一定要了解它是否是 null 安全的,如果不是,則需要我們自己做好非空校驗。

<code>System.out.println(StringUtils.isBlank(null));System.out.println(StringUtils.isNumeric(null));/<code>

(4)當方法返回集合或數組時,避免返回 null,而應是空集合或空數組

返回空集合或空數組時,可以保證調用方法(如size()、length())不會出現 NPE。而且Collections 類中提供了方便的空 List、Set和Map,Collections.EMPTY_LIST、Collections.EMPTY_Set、Collections.EMPTY_MAP。

<code>public List fun(Customer customer){   List result = Collections.EMPTY_LIST;   return result;}/<code>

(5)使用 @NotNull 和 @Nullable 註解

  • @NonNull可以標註在方法、字段、參數之上,表示對應的值不可以為空
  • @Nullable可以標註在方法、字段、參數之上,表示對應的值可以為空

以上兩個註解在程序運行的過程中不會起任何作用,只會在IDE、編譯器、FindBugs檢查、生成文檔的時候提示。

有好幾種 @NotNull 和 @Nullable,我還沒能搞明白,具體怎麼使用我先不講了。但即使不談檢測,單純作為標識也是能夠起到文檔的作用。

(6)避免不必要的裝箱拆箱

如果包裝對象為 null,在拆箱時容易發生 NPE。

<code>Integer integer = null;int i = integer;System.out.println(i);/<code>

(7)定義合理的默認值

定義成員變量時提供合理的默認值。

<code>public class Main {    private List<string> list = new ArrayList<>();    private String s = "";}/<string>/<code>

(8)使用空對象模式

空對象是設計的一種特殊實例,為方法提供默認的行為,例如 Collections中的 EMPTY_List,我們仍能使用它的 size(),會返回 0,而不會拋出 NPE。

再舉個 Jackson 中的例子,當子節點不存在時,path()會返回一個 MissingNode 對象,當調用 MissingNode 對象的 path() 方法是將繼續返回 MissingNode。這樣的鏈式調用將不會拋出 NPE。最後返回後,用戶只需檢查結果是否為 MissingNode 就能判斷是不是找到了。

<code>JsonNode child = root.path("a").path("b");if (child.isMissingNode()) {    //...}/<code>

(9)Optional

Optional 是 Java8 的一個新特性,可以為 null 的容器對象。若值存在,不為 null,則 isPresent()方法會返回 true,調用 get()方法可返回該對象。它所起到的作用是避免我們顯示的進行空值校驗。

舉一個常見的空值校驗示例:

<code>// 最外層public class Outer {    Nested nested;    Nested getNested() {        return nested;    }}/<code>
<code>// 第二層public class Nested {    Inner inner;    Inner getInner() {        return inner;    }}/<code>
<code>// 最底層public class Inner {    String foo;    String getFoo() {        return foo;    }}/<code>

我們通過 Outer 對象訪問 Inner 中的 foo 屬性,若加空值校驗的話,代碼如下:

<code>Outer outer = new Outer();if (outer != null) {    if (outer.nested != null) {        if (outer.nested.inner != null) {            System.out.println(outer.nested.inner.foo);        }    }}/<code>

這種嵌套式的判斷語句在空值校驗中很常見。而使用 Optional 再結合 Java8 的特性 Lambda 表達式、流處理,可以採用鏈式操作,更為簡潔。

<code>Optional.of(new Outer())    .map(Outer::getNested)    .map(Nested::getInner)    .map(Inner::getFoo)    .ifPresent(System.out::println);/<code>

Optional.of() 方法可以返回一個 Optional<outer> 的對象,並將 Outer 對象放在容器內,Optinal.map()方法中,會通過 isPresent() 方法判斷是否為 null,如果為 null,將返回 Optional<outer> 類型的空對象,不影響後續的鏈式調用。是不是很眼熟,這和我們在第 8 點說的空對象模式類似,在 Optional 的實現中也採用了這種模式。/<outer>/<outer>

(10)細心

嘿嘿,湊個第十點吧。

最後祝大家成功避開 NullPointerException,有什麼其他的好建議,歡迎留言交流!


作者:草捏子
原文鏈接:https://juejin.im/post/5e510b85518825496e784b4e


分享到:


相關文章: