還在重複寫空指針檢查代碼?考慮使用 Optional 吧

還在重複寫空指針檢查代碼?考慮使用 Optional 吧

一、前言

如果要給 Java 所有異常弄個榜單,我會選擇將 NullPointerException 放在榜首。這個異常潛伏在代碼中,就像個遙控炸彈,不知道什麼時候這個按鈕會被突然按下(傳入 null 對象)。

還記得剛入行程序員的時候,三天兩頭碰到空指針異常引發的 Bug,解決完一個,又在另一處碰到。那時候師兄就教我,不要相信任何『對象』,特別是別人給你的,這些地方都加上判斷。於是代碼通常為會變成下面這樣:

if(obj!=null){
 // do something
}

有了這個防禦之後,雖然不用再擔心空指針異常,但是過多的判斷語句使得代碼變得臃腫。

假設我們存在如下對象關係

還在重複寫空指針檢查代碼?考慮使用 Optional 吧

原本為了獲取圖中的 name 屬性,原本一句代碼就可以輕鬆完成。

Staff staff=..;
staff.getDepartment().getCompany().getName();

但是很不幸,為了代碼的安全性,我們不得不加入空指針判斷代碼。

Staff staff=..;
if (staff != null) {
 Department department = staff.getDepartment();
 if (department != null) {
 Company company = department.getCompany();
 if (company != null) {
 return company.getName();
 }
 }
}
return "Unknown";

當其中對象為 null 時,可以返回默認值,如上所示。也可以直接拋出其他異常快速失敗。

雖然上面代碼變得更加安全,但是過多嵌套 if 語句降低代碼整體可讀性,提高複雜度。

所幸 Java 8 引入引入一個新類 Java.util.Optional ,依靠 Optional 類提供 API,我們可以寫出既安全又具有閱讀性的代碼。

還在使用 JDK 6 ?那你也別急著關閉這篇文章。可以考慮使用 Guava Optional。不過需要注意的是,Guava Optional API 與 JDK 存在差異,以下以 JDK8 Optional 為例。

二、Optional API

ofNullable

Optional 本質是一個容器,需要我們將對象實例傳入該容器中。 Optional 的構造方法為 private,無法直接使用 new 構建對象,只能使用 Optional 提供的靜態方法創建。

Optional 三個創建方法如下:

  • Optional.of(obj),如果對象為 null,將會拋出 NPE。
  • Optional.ofNullable(obj),如果對象為 null,將會創建不包含值的 empty Optional 對象實例。
  • Optional.empty() 等同於 Optional.ofNullable(null)
  • -
還在重複寫空指針檢查代碼?考慮使用 Optional 吧


只有在確定對象不會為 null 的情況使用 Optional#of,否則建議使用 Optional#ofNullable方法。

isPresent

對象實例存入 Optional 容器中之後,最後我們需要從中取出。Optional#get 方法用於取出內部對象實例,不過需要注意的是,如果是 empty Optional 實例,由於容器內沒有任何對象實例,使用 get 方法將會拋出 NoSuchElementException 異常。

為了防止異常拋出,可以使用 Optional#isPresent 。這個方法將會判斷內部是否存在對象實例,若存在則返回 true。

示例代碼如下:

Optional optCompany = Optional.ofNullable(company);
// 與直接使用空指針判斷沒有任何區別
if (optCompany.isPresent()) {
 System.out.println(optCompany.get().getName());
}

仔細對比,可以發現上面用法與空指針檢查並無差別。剛接觸到 Optional ,看到很多文章介紹這個用法,那時候一直很疑惑,這個解決方案不是更加麻煩?

後來接觸到 Optional 其他 API,我才發現這個類真正有意義是下面這些 API。如果使用過 Java8 Stream 的 API,下面 Optional API 你將會很熟悉。

ifPresent

通常情況下,空指針檢查之後,如果對象不為空,將會進行下一步處理,比如打印該對象。

Company company = ...;
if(company!=null){
 System.out.println(company);
}

上面代碼我們可以使用 Optional#ifPresent 代替,如下所示:

Optional optCompany = ...;
optCompany.ifPresent(System.out::println);

使用 ifPresent 方法,我們不用再顯示的進行檢查,如果 Optional 為空,上面例子將不再輸出。

filter

有時候我們需要某些屬性滿足一定條件,才進行下一步動作。這裡假設我們當 Company name 屬性為 Apple,打印輸出 ok。

if (company != null && "Apple".equals(company.getName())) {
 System.out.println("ok");
}

下面使用 Optional#filter 結合 Optional#ifPresent 重寫上面的代碼,如下所示:

Optional companyOpt=...;
companyOpt
 .filter(company -> "Apple".equals(company.getName()))
 .ifPresent(company -> System.out.println("ok"));

filter 方法將會判斷對象是否符合條件。如果不符合條件,將會返回一個空的 Optional 。

orElseThrow

當一個對象為 null 時,業務上通常可以設置一個默認值,從而使流程繼續下去。

String name = company != null ? company.getName() : "Unknown";

或者拋出一個內部異常,記錄失敗原因,快速失敗。

if (company.getName() == null) {
 throw new RuntimeException();
}
 

Optional 類提供兩個方法 orElse 與 orElseThrow ,可以方便完成上面轉化。

// 設置默認值
String name=companyOpt.orElse(new Company("Unknown")).getName();
// 拋出異常
String name=companyOpt.orElseThrow(RuntimeException::new).getName();

如果 Optional 為空,提供默認值或拋出異常。

flatMap

熟悉 Java8 Stream 同學的應該瞭解,Stream#map 方法可以將當前對象轉化為另外一個對象, Optional#map 方法也與之類似。

Optional optCompany = ...;
Optional nameopt = optCompany.map(Company::getName);

map 方法可以將原先 Optional 轉變成 Optional ,此時 Optional 內部對象變成 String 類型。如果轉化之前 Optional 對象為空,則什麼也不會發生。

另外 Optional 還有一個 flatMap 方法,兩者區別見下圖。

還在重複寫空指針檢查代碼?考慮使用 Optional 吧

Department#getCompany 返回對象為 Optional

三、代碼重構

上面我們學習了 Optional 類主要 API ,下面我們使用 Optional 重構文章剛開頭的代碼。為了方便讀者對比,將上面的代碼複製了下來。

代碼重構前:

if (staff != null) {
 Department department = staff.getDepartment();
 if (department != null) {
 Company company = department.getCompany();
 if (company != null) {
 return company.getName();
 }
 }
}
return "Unknown";

首先我們需要將 Staff ,Department 修改 getter 方法返回結果類型改成 Optional。

public class Staff {
 private Department department;
 public Optional getDepartment() {
 return Optional.ofNullable(department);
 }
 ...
}
public class Department {
 private Company company;
 public Optional getCompany() {
 return Optional.ofNullable(company);
 }
 ...
}
public class Company {
 private String name;
 public String getName() {
 return name;
 }
 ...
}

然後綜合使用 Optional API 重構代碼如下:

Optional staffOpt =...;
staffOpt
 .flatMap(Staff::getDepartment)
 .flatMap(Department::getCompany)
 .map(Company::getName)
 .orElse("Unknown");

可以看到重構之後代碼利用 Optional 的 Fluent Interface,以及 lambda 表達式,使代碼更加流暢連貫,並且提高代碼整體可讀性。

四、幫助文章

1、https://www.oracle.com/technetwork/articles/java/java8-optional-2175753.html

3、https://community.oracle.com/docs/DOC-991686

3、Java8 in Action

本文由博客一文多發平臺 https://openwrite.cn?from=article_bottom 發佈!


分享到:


相關文章: