Optional 是 Java 8 引進的一個新特性,通常用於緩解常見的空指針異常問題。Brian Goetz (Java語言設計架構師)對Optional設計意圖的原話如下:
Optional is intended to provide a limited mechanism for library method return types where there needed to be a clear way to represent “no result," and using null for such was overwhelmingly likely to cause errors.
這句話突出了三個點:
1、Optional 是用來作為方法返回值的
2、Optional 是為了清晰地表達返回值中沒有結果的可能性
3、且如果直接返回 null 很可能導致調用端產生錯誤(尤其是NullPointerException)
Optional 的機制類似於 Java 的受檢異常,強迫API調用者面對沒有返回值的現實。參透 Optional 的設計意圖才能學會正確得使用它。下面介紹一下Optional方法以及圍繞這三個點闡述 Optional的最佳實踐。
一、Optional的相關方法介紹
1、JDK 提供三個靜態方法來構造一個Optional:我們主要記住這個就行了 - Optional.ofNullable(T value)
(1)Optional.of(T value),該方法通過一個非 null 的 value 來構造一個 Optional,返回的 Optional 包含了 value 這個值。對於該方法,傳入的參數一定不能為 null,否則便會拋出 NullPointerException。
(2)Optional.ofNullable(T value),該方法和 of 方法的區別在於,傳入的參數可以為 null。該方法會判斷傳入的參數是否為 null,如果為 null 的話,返回的就是 Optional.empty()。
(3)Optional.empty(),該方法用來構造一個空的 Optional,即該 Optional 中不包含值,其實底層實現還是 如果 Optional 中的 value 為 null 則該 Optional 為不包含值的狀態,然后在 API 層面將 Optional 表現的不能包含 null 值,使得 Optional 只存在 包含值 和 不包含值 兩種狀態。
2、 ifPresent —— 如果 Optional 中有值,則對該值調用 consumer.accept,否則什么也不做。
3、orElse —— 如果 Optional 中有值則將其返回,否則返回 orElse 方法傳入的參數。
4、orElseGet —— 與 orElse 方法的區別在於,orElseGet 方法傳入的參數為一個 Supplier 接口的實現 —— 當 Optional 中有值的時候,返回值;當 Optional 中沒有值的時候,返回從該 Supplier 獲得的值。
User user = Optional.ofNullable(getUserById(id)) .orElse(new User(0, "Unknown")); User user = Optional.ofNullable(getUserById(id)) .orElseGet(() -> new User(0, "Unknown"));
5、orElseThrow —— 與 orElse 方法的區別在於,orElseThrow 方法當 Optional 中有值的時候,返回值;沒有值的時候會拋出異常,拋出的異常由傳入的 exceptionSupplier 提供。
User user = Optional.ofNullable(getUserById(id)) .orElseThrow(() -> new EntityNotFoundException("id 為 " + id + " 的用戶沒有找到"));
舉一個 orElseThrow 的用途:在 SpringMVC 的控制器中,我們可以配置統一處理各種異常。查詢某個實體時,如果數據庫中有對應的記錄便返回該記錄,否則就可以拋出 EntityNotFoundException ,處理 EntityNotFoundException 的方法中我們就給客戶端返回Http 狀態碼 404 和異常對應的信息 —— orElseThrow 完美的適用於這種場景。
@RequestMapping("/{id}") public User getUser(@PathVariable Integer id) { Optional<User> user = userService.getUserById(id); return user.orElseThrow(() -> new EntityNotFoundException("id 為 " + id + " 的用戶不存在")); } @ExceptionHandler(EntityNotFoundException.class) public ResponseEntity<String> handleException(EntityNotFoundException ex) { return new ResponseEntity<>(ex.getMessage(), HttpStatus.NOT_FOUND); }
6、map —— 如果當前 Optional 為 Optional.empty,則依舊返回 Optional.empty;否則返回一個新的 Optional,該 Optional 包含的是:函數 mapper 在以 value 作為輸入時的輸出值。而且我們可以多次使用map操作:
Optional<String> username = Optional .ofNullable(getUserById(id)) .map(user -> user.getUsername()) .map(name -> name.toLowerCase()) .map(name -> name.replace('_', ' '));
7、filter 方法接受一個 Predicate 來對 Optional 中包含的值進行過濾,如果包含的值滿足條件,那么還是返回這個 Optional;否則返回 Optional.empty。
Optional<String> username = Optional.ofNullable(getUserById(id)) .filter(user -> user.getId() < 10) .map(user -> user.getUsername());
8、or 方法的作用是,如果一個 Optional 包含值,則返回自己;否則返回由參數 supplier 獲得的 Optional
9、ifPresentOrElse 方法的用途是,如果一個 Optional 包含值,則對其包含的值調用函數 action,即 action.accept(value),這與 ifPresent 一致;與 ifPresent 方法的區別在於,ifPresentOrElse 還有第二個參數 emptyAction —— 如果 Optional 不包含值,那么 ifPresentOrElse 便會調用 emptyAction,即 emptyAction.run()
10、stream 方法的作用就是將 Optional 轉為一個 Stream,如果該 Optional 中包含值,那么就返回包含這個值的 Stream;否則返回一個空的 Stream(Stream.empty())。
二、Optional 是用來作為方法返回值的
1、不要濫用 Optional API
有的同學知道了一些Optional的API后就覺得找到了一把錘子,看到什么都像釘子,於是寫出了以下這種代碼
String finalStatus = Optional.ofNullable(status).orElse("PENDING") // 這種寫法不僅降低了代碼可讀性還無謂得創建了一個Optional對象(浪費性能) // 以下是同等功能但更簡潔更可讀的實現
String finalStatus = status == null ? "PENDING" : status;
2、不要使用Optional作為Java Bean實例域的類型,因為 Optional 沒有實現 Serializable 接口(不可序列化)
3、不要使用 Optional 作為類構造器參數
4、不要使用 Optional 作為Java Bean Setter方法的參數
原因除了上面第二點提到的 Optional 是不可序列化的,還有降低了可讀性。
既然 setter是用於給Java Bean 屬性賦值的, 為什么還無法確定里面的值是不是空 ? 如果為空,為何不直接賦值 null (或者對應的空值) ?
但相反的是,對於可能是空值 Java Bean 屬性的 Getter 方法返回值使用 Optional 類型是很好的實踐。
@Entity public class Customer implements Serializable {private String postcode; // optional field, thus may be null
public Optional<String> getPostcode() { return Optional.ofNullable(postcode); } public void setPostcode(String postcode) { this.postcode = postcode; } ... }
由於getter返回的是Optional,外部調用時就意識到里面可能是空結果,需要進行判斷。注意:對值可能為 null 的實例域的 getter 才需要使用 Optional。
5、不要使用Optional作為方法參數的類型
首先,當參數類型為Optional時,所有API調用者都需要給參數先包一層Optional(額外創建一個Optional實例)浪費性能 —— 一個Optional對象的大小是簡單引用的4倍。其次,當方法有多個Optional參數時,方法簽名會變得更長,可讀性更差。
三、Optional 是為了清晰地表達返回值中沒有結果的可能性
1、不要給Optional變量賦值 null,而應該用 Optional.empty() 表達空值
2、確保Optional內有值才能調用 get() 方法
如果不檢查Optional是否為空值就直接調用get() 方法,就讓 Optional 失去了意義 —— Optional 是為了清晰地表達返回值中沒有結果的可能性,強迫API調用者面對沒有返回值的現實並做檢查。
目前Java 8編譯器並不會對這種情況報錯,但是 IDE 已經可以識別並警告
// 所以避免
Optional<Cart> cart = ... ; // this is prone to be empty
... // if "cart"is empty then this code will throw a java.util.NoSuchElementException
Cart myCart = cart.get(); // 而應該
if (cart.isPresent()) { Cart myCart = cart.get(); ... // do something with "myCart"
} else { ... // do something that doesn't call cart.get()
}
3、盡量使用 Optional 提供的快捷API 避免手寫 if-else 語句
在一些場景下, Optional.orElse() Optional.orElseGet() Optional.ifPresent() 可以避免手寫 if-else 語句,使代碼更簡潔。
public static final String USER_STATUS = "UNKNOWN"; ... public String findUserStatus(long id) { Optional<String> status = ... ; // prone to return an empty Optional
return status.orElse(USER_STATUS); } public String computeStatus() { ... // some code used to compute status
} public String findUserStatus(long id) { Optional<String> status = ... ; // prone to return an empty Optional // computeStatus() is called only if "status" is empty
return status.orElseGet(this::computeStatus); } Optional<String> status ... ; status.ifPresent(System.out::println);
4、使用 equals 而不是 == 來比較 Optional 的值,Optional 的 equals 方法已經實現了內部值比較
總結:
(1)Optional 盡量只用來作為方法返回值類型
(2)調用了返回值為Optional的方法后,一定要做空值檢查
(3)不要過度使用 Optional 避免降低代碼可讀性和性能
(4)查閱並適當使用 Optional API
四、常用 API 梳理
這里梳理一下Optional的API,標出幾個重點且適合我們使用的方法:
最重要的就是學會3個方法:
1、如何包裝value:Optional.ofNullable()
2、逐層安全地拆解value:map()
3、最終返回:orElse()
這三個搭配就是最佳實踐。其他可能會用的方法就是orElseGet()、orElsethrow(),其他的不常用或者說不好用。
五、Optional的設計思想實現
詳見這篇文章,寫的挺清晰的:https://zhuanlan.zhihu.com/p/338128121