Java 8 Optional:優雅地避免 NPE
本篇文章將詳細介紹 Optional 類,以及如何用它消除程式碼中的 null 檢查。在開始之前首先來看下什麼是 NPE,以及在 Java 8 之前是如何處理 NPE 問題的。
空指標異常(NullPointException,簡稱 NPE)可以說是所有 Java 程式設計師都遇到過的一個異常,雖然 Java 從設計之初就力圖讓程式設計師脫離指標的苦海,但是指標確實是實際存在的,而 Java 設計者也只能是讓指標在 Java 語言中變得更加簡單易用,而不能完全剔除,所以才有了常見對的關鍵字 null。
避免使用 null 檢查
空指標異常是一個執行時異常,對於這一類異常,如果沒有明確的處理方式,那麼最佳實踐在於讓程式早點掛掉。當異常真的發生的時候,處理方式也很簡單,在存在異常的地方新增一個 if 語句判定即可。比如下面的程式碼:
public String bindUserToRole(User user) { if (user == null) { return; } String roleId = user.getRoleId(); if (roleId == null) { return; } Role = roleDao.findOne(roleId); if (role != null) { role.setUserId(user.getUserId()); roleDao.save(role); } }
但是這樣的應對方式會讓程式出現越來越多的 null 判定,一個良好的程式設計,應該讓程式碼中儘量少出現 null 關鍵字,因此 Java 8 引入 Optional 類來避免 NPE 問題,同時也提升了程式碼的美觀度。但並不是對 null 關鍵字的一種替代,而是對於 null 判定提供了一種更加優雅的實現,從而避免 NPE 問題。
Optional 類
為了更好的解決和避免常見的 NPE 問題,Java 8 中引入了一個新的類 java.util.Optional
建立 Optional 物件
Optional 類提供類三個方法用於例項化一個 Optional 物件,它們分別為 empty()、of()、ofNullable(),這三個方法都是靜態方法,可以直接呼叫。
empty() 方法用於建立一個沒有值的Optional物件:
Optional<String> emptyOpt = Optional.empty();
empty() 方法建立的物件沒有值,如果對 emptyOpt 變數呼叫 isPresent() 方法會返回 false,呼叫 get() 方法丟擲 NPE 異常。
of() 方法使用一個非空的值建立Optional物件:
String str = "Hello World";
Optional<String> notNullOpt = Optional.of(str);
ofNullable() 方法接收一個可以為null的值:
Optional<String> nullableOpt = Optional.ofNullable(str);
如果 str 的值為 null,得到的 nullableOpt 是一個沒有值的 Optional 物件。
獲取 Optional 物件中的值
如果我們要獲取 User 物件中的 roleId 屬性值,常見的方式是直接獲取:
String roleId = null;
if (user != null) {
roleId = user.getRoleId();
}
使用 Optional 中提供的 map() 方法可以更簡單地實現:
Optional<User> userOpt = Optional.ofNullable(user);
Optional<String> roleIdOpt = userOpt.map(User::getRoleId);
使用 orElse()方法獲取值
Optional 類還包含其他方法用於獲取值,這些方法分別為:
- orElse():如果有值就返回,否則返回一個給定的值作為預設值
- orElseGet():與 orElse() 方法作用類似,區別在於生成預設值的方式不同。該方法接受一個 Supplier<? extends T> 函式式介面引數,用於生成預設值
- orElseThrow():與前面介紹的 get() 方法類似,當值為 null 時呼叫這兩個方法都會丟擲 NPE 異常,區別在於該方法可以指定丟擲的異常型別
下面來看看這三個方法的具體用法:
String str = "Hello World";
Optional<String> strOpt = Optional.of(str);
String orElseResult = strOpt.orElse("Hello BeiJing");
String orElseGet = strOpt.orElseGet(() -> "Hello BeiJing");
String orElseThrow = strOpt.orElseThrow(
() -> new IllegalArgumentException("Argument 'str' cannot be null or blank."));
此外,Optional 類還提供了一個 ifPresent() 方法,該方法接收一個 Consumer<? super T> 函式式介面,一般用於將資訊列印到控制檯:
Optional<String> strOpt = Optional.of("Hello World");
strOpt.ifPresent(System.out::println);
使用 filter() 方法過濾
filter() 方法可用於判斷 Optional 物件是否滿足給定條件,一般用於條件過濾:
Optional<String> optional = Optional.of("[email protected]");
optional = optional.filter(str -> str.contains("wupx"));
在上面的程式碼中,如果 filter() 方法中的 Lambda 表示式成立,filter() 方法會返回當前 Optional 物件值,否則,返回一個值為空的 Optional 物件。
關於 Optional 使用建議:
- 儘量避免在程式中直接呼叫 Optional 物件的 get() 和 isPresent() 方法
- 避免使用 Optional 型別宣告實體類的屬性
Optional 實踐
上面提到建立 Optional 物件有三個方法,empty() 方法比較簡單,主要是 of() 和 ofNullable() 方法。當你確定一個物件不可能為 null 的時候,應該使用 of() 方法,否則,儘可能使用 ofNullable() 方法,比如:
public static void method(Role role) {
// 當Optional的值通過常量獲得或者通過關鍵字 new 初始化,可以直接使用 of() 方法
Optional<String> strOpt = Optional.of("Hello World");
Optional<User> userOpt = Optional.of(new User());
// 方法引數中role值不確定是否為null,使用 ofNullable() 方法建立
Optional<Role> roleOpt = Optional.ofNullable(role);
}
orElse() 方法的使用
return str != null ? str : "Hello World"
上面的程式碼表示判斷字串 str 是否為空,不為空就返回,否則,返回一個常量。使用 Optional 類可以表示為:
return strOpt.orElse("Hello World")
簡化 if-else
User user = ...
if (user != null) {
String userName = user.getUserName();
if (userName != null) {
return userName.toUpperCase();
} else {
return null;
}
} else {
return null;
}
上面的程式碼可以簡化成:
User user = ...
Optional<User> userOpt = Optional.ofNullable(user);
return userOpt.map(User::getUserName)
.map(String::toUpperCase)
.orElse(null);
注意事項
Optional 是一個 final 類,未實現任何介面,Optional 不能序列化,不能作為類的欄位(field),所以當我們在利用該類包裝定義類的屬性的時候,如果我們定義的類有序列化的需求,那麼因為 Optional 沒有實現 Serializable 介面,這個時候執行序列化操作就會有問題:
import java.util.Optional;
import lombok.Data;
@Data
public class User implements Serializable {
private String name;
private String gender;
private Optional<String> phone; // 不能序列化
}
可以通過自己實現 getter 方法,使 Lomok 不自動生成,如下:
import java.util.Optional;
import lombok.Data
@Data
public class User implements Serializable {
private String name;
private String gender;
private String phone;
public Optional<String> getPhone() {
return Optional.ofNullable(phone);
}
}
總結
Java 8 中 Optional 類可以讓我們以函數語言程式設計的方式處理 null 值,拋棄了 Java 8 之前需要巢狀大量 if-else 程式碼塊,使程式碼可讀性有了很大的提高,但是應儘量避免使用 Optional 型別宣告實體類的屬性