1. 程式人生 > 其它 >NPE風險(空指標異常)

NPE風險(空指標異常)

1. 前言

對於 Java 開發者來說,null 是一個令人頭疼的型別,一不小心就會發生 NPE (空指標) 問題。也是 Java 語言為人詬病的一個重要原因之一。在我們消除可惡的 NPE 問題之前我們要回顧一下 Java 中 null 的概念。

2. Java 中的 null

翻譯自 Oracle Java 文件[1]

Java 語言中有兩種型別,一種是 基本型別 ,另一種是 引用型別。
還有一種沒有名字的特殊型別,即表示式 null 。
由於 null 型別沒有名稱,所以不可能宣告為 null 型別的變數或者轉換為 null 型別。
null 引用是null 型別表示式唯一可能的值。
null
引用可以轉換為任意引用型別。 事實上,程式設計師可以忽略null型別,可以認為null僅僅是一個可以成為任何引用型別的特殊符號。

從上面的描述我們可以瞭解到,其實 null 僅僅是一個關鍵字標識量,既不是一種型別也不算物件,無法直接宣告 null 和被轉換為 null,僅僅只能被引用,null 可以轉換為任何引用型別。當一個 Java 引用型別物件被引用為 null 時代表當前物件不引用物件,並沒有為其分配記憶體。 這也是我們在沒有引用的物件上呼叫方法出現空指標的根本原因。大多數情況下 Java 開發者使用 null 是為了表示某種不存在的意思。

3. NPE 問題的解決

很多時候我們對資料是否存在有自己的期望,但是這種期望並不能直接被我們掌控,一個返回值為 null 所表達的意思並不明確過於模糊,往往通過是否判斷為 null

來規避空指標問題。於是 Google 工程師在他們的 Guava 工具類庫中設計了 Optional<T> 來解決 null 不可控的問題。讓你在不得不使用 null 的時候,可以更加簡便明確的使用 null 並幫助你避免直接使用 null 帶來的問題。Java 8 將此設計吸收。我們可以直接使用 Java 提供的 Optional 來解決空指標問題。接下來我們來研究一下 Java 8 中的 Optional

4. Java 8 中的 Optional

Java 8 中的 Optional 是一個可選值的包裝類。它的意義不僅僅幫我們簡化了 NPE 問題的處理,同時也是 Java

函數語言程式設計的一個重要輔助。我們接下來將對其 API 進行講解以幫助你在實際開發中使用他們。

4.1 Optional 宣告

Optional 只能通過靜態方法來宣告。它提供了三個靜態方法:

empty() 返回一個值為 nullOptional 例項

 Optional<Object> empty = Optional.empty();

of(T) 返回一個值不為 nullOptional 例項

Optional<String> nonNull = Optional.of("Felordcn");

ofNullable() 返回一個值可能為 nullOptional 例項

// value 值來自其它不確定的來源
 String value = SomeApi.source();
 // 可能為 null
 Optional<String> nullable = Optional.ofNullable(value);
 // 也可能不為 null
 Optional<String>  hasValue = Optional.ofNullable(value);

ifPresent(Consumer) 如果值存在則該值被消費函式 Consumer 消費 , 否則不做任何事情。isPresent() 加強版

//  非空打印出字串
      nullable.ifPresent(System.out::println);

     //等同於
      if (nullable.isPresent()) {
                 System.out.println(nonNull);
      }

filter(Predicate) 如果值滿足斷言函式 Predicate 則返回該 Optional,否則返回 Optional.empty()

Optional<String> nonNull = Optional.of("Felordcn");
 Optional<String> felord = nonNull.filter(s -> s.startsWith("Felord"));
 // str = "Felordcn"
 String str = felord.get();

map(Function) 獲取元素某個屬性的 Optional 。如果該屬性為 null 返回 Optional.empty() ,否則返回對應值的 Optional

Optional<User> userOpt = Optional.ofNullable(user);
//  username 為空 則為 空  Optional
 Optional<String> usernameOpt = userOpt.map(User::getUsername);

flatMap(Function) 有時候我們會返回 Optional<Optional<T>> 非常不便於處理,我們需要將元素展開,可使用該方法處理,參考 Stream Api 中的相關方法

orElse(other) 如果 Optional 的值存在,返回 Optional, 否則指定一個 Optional

orElseGet(Supplier) 如果 Optional 的值存在,返回 Optional, 否則指定一個執行 Supplier 函式來獲取值

orElseThrow(Supplier<? extends Throwable>) 如果 Optional 的值存在,返回 Optional, 否則丟擲一個指定 Supplier 函式提供的異常

4.3 Java 9 中的新 API

or(Supplier) orElseGet 的改進型別。不單單返回具體的值,而可以函式式的返回 Optional

stream()OptionalStream 打通

ifPresentOrElse(Consumer) ifPresent 方法提供了有值後的消費邏輯而沒有值的邏輯沒有提供入口。新方法 ifPresentOrElse 彌補了這一缺陷

5. Optional 的使用誤區

Optional 很香但是也不能濫用。一個危險的舉動就是將 Optional 作為入參傳遞給方法。因為入參是不可控的,你無法保證入參中的 Optional 是否為 null。這恰恰違背了 Optional 的本意。所以儘量在表示式中使用 Optional 或者在返回值中使用,而不是在方法的引數中使用 Optional

6. 總結

今天對 Optional 進行講解。從 Optional 的設計本意到其常用的方法。我們也對 OptionalJava 9 中的新 API 進行了介紹。另外 Optional 也不是萬能的,合理的使用才能發揮其優勢。希望今天的文章對你有用。

參考資料

[1]

Oracle Java 文件: https://docs.oracle.com/javase/specs/jls/se8/html/jls-4.html#jls-4.1

Java 是如何優雅地處理NPE問題的 - 雲+社群 - 騰訊雲 (tencent.com)