淺析Java8新特性-Optional方法介紹(Optional.ofNullable-構造方法、map-逐層安全地拆解value、filter-過濾值、orElse/orElseThrow-最終返回、stream-轉為流)及常用實踐(僅作為方法返回值、清晰地表達返回值中沒有結果的可能性、勿濫用Optional)、Optional的設計思想實現


  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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM