Java中避免空指標的幾個方法!

語言: CN / TW / HK

theme: vuepress

前言

Java 程式設計中哪個異常是你印象最深刻的,那 NullPointerException 空指標可以說是臭名昭著的。不要說初級程式設計師會碰到,即使是中級,專家級程式設計師稍不留神,就會掉入這個坑裡。

Java 中任何物件都有可能為空,當我們呼叫空物件的方法時就會丟擲 NullPointerException 空指標異常,這是一種非常常見的錯誤型別。我們可以使用若干種方法來避免產生這類異常,使得我們的程式碼更為健壯。

空指標出現的幾種情況

1、呼叫了空物件的例項方法

```java public class Person { // 屬性 private String name; private int age;

public void print(){
    System.out.println("This is Person Class");
}

public String read(){
    System.out.println("This is person read");
    return null;
}

public static void main(String[] args)
{
    Person person = null;
    //呼叫了空物件的例項方法
    person.print();
}

} ```

2、訪問了空物件的屬性

java Person person = null; //呼叫了空物件的屬性 System.out.println(person.age);

3、當陣列是一個空物件時,取它的長度

java Person person = null; //當陣列是一個空物件時,取它的長度 System.out.println(person.name.length());

4、方法的返回值是 null, 呼叫方直接去使用

java Person person = new Person(); //方法的返回值是 null, 呼叫方直接去使用 System.out.println(person.read().contains("Java"));

如何從根源避免空指標

1、使用之前一定要初始化,或檢查是否初始化;

2、儘量避免在函式中返回 NULL, 或給出詳細的註釋;

3、外部傳值,除非有明細的說明,否則,一定要判斷是否為 NULL。

equals和equalsIgnoreCase()方法

Object類中的equals 方法在非空物件引用上實現相等關係,具有對稱性 x.equals(y) 和 y.equals(x) 結果是一樣的,但當x == null時會丟擲空指標異常。

例如:

```java String x = null; String y = "world"; if(x.equals(y)){ // java.lang.NullPointerException

}

if(y.equals(x)){//即便 x 是 null也能避免 NullPointerException

} ```

所以我們要把確定不為null的物件或值放在前面。

valueOf()和toString()

呼叫null物件的toString()會丟擲空指標異常,使用valueOf()可以獲得相同的值,傳遞一個 null 給 valueOf() 將會返回null。尤其是在那些包裝類,像Integer、Float、Double和BigDecimal。

例如:

java Integer i = null; System.out.println(i.toString()); // 丟擲NullPointerException異常 System.out.println(String.valueOf(i)); // 返回 null不會出現異常

Optional 型別

Java 8 引入了 Optional 型別,我們可以用它來對函式的返回值進行包裝。這種方式的優點是可以明確定義該方法是有可能返回空值的,因此呼叫方必須做好相應處理,這樣也就不會引發空指標異常。但是,也不可避免地需要編寫更多程式碼,而且會產生很多垃圾物件,增加 GC 的壓力,因此在使用時需要酌情考慮。

在業務系統中,物件中巢狀物件是經常發生的場景,如下示例程式碼:

java // 最外層物件 class Outer { Nested nested; Nested getNested() { return nested; } } // 第二層物件 class Nested { Inner inner; Inner getInner() { return inner; } } // 最底層物件 class Inner { String foo; String getFoo() { return foo; } }

業務中,假設我們需要獲取 Outer 物件對底層的 Inner 中的 foo 屬性,我們必須寫一堆的非空校驗,來防止發生 NullPointerException

java // 繁瑣的程式碼 Outer outer = new Outer(); if (outer != null && outer.nested != null && outer.nested.inner != null) { System.out.println(outer.nested.inner.foo); }

在 Java8 中,我們有更優雅的解決方式,那就是使用 Optional是說,我們可以在一行程式碼中,進行流水式的 map 操作。而 map 方法內部會自動進行空校驗

java Optional.of(new Outer()) .map(Outer::getNested) .map(Nested::getInner) .map(Inner::getFoo .ifPresent(System.out::println); // 如果不為空,最終輸出 foo 的值

suppiler 函式自定義增強 API

我們可以利用 suppiler 函式來出一個終極解決方案:

java public static <T> Optional<T> resolve(Supplier<T> resolver) { try { T result = resolver.get(); return Optional.ofNullable(result); } catch (NullPointerException e) { // 可能會丟擲空指標異常,直接返回一個空的 Optional 物件 return Optional.empty(); } }

利用上面的 resolve 方法來重構上述的非空校驗程式碼段:

java Outer obj = new Outer(); // 直接呼叫 resolve 方法,內部做空指標的處理 resolve(() -> obj.getNested().getInner().getFoo()); .ifPresent(System.out::println); // 如果不為空,最終輸出 foo 的值

使用null安全的方法和庫

有很多開源庫已經為您做了繁重的空指標檢查工作。其中最常用的一個的是Apache commons 中的StringUtils。你可以使用StringUtils.isBlank()isNumeric()isWhiteSpace()以及其他的工具方法而不用擔心空指標異常。

```java //StringUtils方法是空指標安全的,他們不會丟擲空指標異常 System.out.println(StringUtils.isEmpty(null)); System.out.println(StringUtils.isBlank(null)); System.out.println(StringUtils.isNumeric(null)); System.out.println(StringUtils.isAllUpperCase(null));

輸出: true true false false ```

斷言

斷言是用來檢查程式的安全性的,在使用之前進行檢查條件,如果不符合條件就報異常,符合就繼續。

Java 中自帶的斷言關鍵字:assert,如:

java assert name == null : "名稱不能為空";

輸出:

java Exception in thread "main" java.lang.AssertionError: 名稱不正確

不過預設是不啟動斷言檢查的,需要要帶上 JVM 引數:-enableassertions 才能生效。

Java 中這個用的很少,建議使用 Spring 中的,更強大,更方便好用。

Spring中的用法:

java Assert.notNull(name,"名稱不能為空");

建立返回空集合而不是null的方法

一個非常好的技術是建立返回一個空集合的方法,而不是一個null值。你的應用程式的程式碼可以遍歷空集合並使用它的方法和欄位,而不會丟擲一個NullPointerException。Collections類提供了方便的空 List,Set 和 Map: Collections.EMPTY_LISTCollections.EMPTY_SETCollections.EMPTY_MAP

例如:

```java public class Example { private static List numbers = null;

public static List getList() { if (numbers == null){ return Collections.EMPTY_LIST; } else { return numbers; } } } ```

賦值時自動拆箱出現空指標

1、變數賦值自動拆箱出現的空指標

java Long i = null; //變數賦值自動拆箱出現的空指標 long j = i;

Long 自動拆箱為 long 實質是呼叫了 longValue 方法,由於以上的 Long 型別變數值為 null,自動拆箱就會報 NPE。

2、方法傳參時自動拆箱引起空指標異常

```java public static int add(int x, int y) { return x + y; }

public static void main(String[] args) { Integer left = null; Integer right = null; //方法傳參時自動拆箱引起空指標異常 System.out.println(add(left, right)); } ```

以上自動拆箱報 NPE,原因在於裝箱的值為 null,呼叫 null 的任何 方法都會報錯。

規避指南:

  • 基本資料型別優於包裝器型別,優先考慮使用基本型別。
  • 對於不確定的包裝器型別,一定要校驗是否為 NULL。

基本資料型別比包裝器型別更加節省時間與空間。

結語

關於怎麼有效避免空指標,其實還有更多,如何避免空指標,一是要注意程式碼編寫規範,二是要提高程式碼素養。我們一起加油~