Java 8th 函數式編程:Optional 類型

NullPointException 能夠說是全部 java 程序員都遇到過的一個異常,雖然 java 從設計之初就力圖讓程序員脫離指針的苦海,可是指針確實是實際存在的,而 java 設計者也只能是讓指針在 java 語言中變得更加簡單、易用,而不能徹底的將其剔除,因此纔有了咱們平常所見到的關鍵字 nulljava

空指針異常是一個運行時異常,對於這一類異常,若是沒有明確的處理策略,那麼最佳實踐在於讓程序早點掛掉,可是不少場景下不是開發人員沒有具體的處理策略,而是根本沒有意識到空指針異常的存在。當異常真的發生的時候,處理策略也很簡單,在存在異常的地方添加一個 if 語句斷定便可,可是這樣的應對策略會讓咱們的程序出現愈來愈多的 null 斷定。一個良好的程序設計應該讓代碼中儘可能少出現 null 關鍵字,而 8th 所提供的 Optional 類則在減小 NullPointException 的同時,也提高了代碼的美觀度。但首先咱們須要明確的是它並 不是對 null 關鍵字的替代策略,而是對於 null 斷定提供了一種更加優雅的實現,從而儘量地避免 NullPointException程序員

下面經過一個小示例直觀感覺一下,假設咱們須要返回一個字符串的長度,若是不借助第三方工具類,咱們須要調用 str.length() 方法:工具

if(null == str) { // 空指針斷定
    return 0;
}
return str.length();

若是採用 Optional 類,實現以下:this

return Optional.ofNullable(str).map(String::length).orElse(0);

Optional 的代碼相對更加簡潔,當代碼量較大時,咱們很容易忘記進行 null 斷定,可是使用 Optional 類則會避免這類問題。設計

一. 基本使用

1.1 Optional 對象的建立

  • 建立空對象
Optional<String> optStr = Optional.empty();

上面的示例代碼調用 empty() 方法建立了一個空的 Optional<String> 對象型。指針

  • 建立對象:不容許我空

Optional 提供了方法 of() 用於建立非空對象,該方法要求傳入的參數不能爲空,不然拋 NullPointException,示例以下:code

Optional<String> optStr = Optional.of(str);  // 當str爲null的時候,將拋出NullPointException
  • 建立對象:容許爲空

若是不能肯定傳入的參數是否存在 null 值的可能性,則能夠用 Optional 的 ofNullable() 方法建立對象,若是入參爲 null 則建立一個空對象。示例以下:對象

Optional<String> optStr = Optional.ofNullable(str);  // 若是str是null,則建立一個空對象

1.2 流式數據處理

流式數據處理也是 8th 給咱們帶來的一個重量級新特性,讓咱們對集合的操做變得更加簡潔和高效,本系列下一篇將對流式數據處理進行全面的講解。Optional 類也提供了兩個基本的流失處理:映射和過濾。接口

爲了演示,咱們設計了一個 User 類,以下:開發

public class User {
    private long id;
    private String name;
    private int age;
    private Optional<Long> phone;
    private Optional<String> email;

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // 省略setter和getter
}

手機和郵箱不是一我的的必須有的,因此咱們利用 Optional 類定義。

  • 映射:map 與 flatMap

映射是將輸入轉換成另一種形式的輸出的操做,好比前面例子中咱們輸入字符串,而輸出的是字符串的長度,這就是一種映射,咱們利用方法 map() 進行實現。假設咱們但願得到一我的的姓名,咱們能夠以下實現:

String name = Optional.ofNullable(user).map(User::getName).orElse("no name");

這樣當入參 user 不爲空的時候則返回其 name,不然返回 no name。如我咱們但願經過上面方式獲得 phone 或 email,利用上面的方式則行不通了,由於 map 以後返回的是 Optional,咱們把這種稱爲 Optional 嵌套,咱們必須再 map 一次才能拿到咱們想要的結果:

long phone = optUser.map(User::getPhone).map(Optional::get).orElse(-1L);

其實這個時候更好的方式是利用 flatMap,一步拿到咱們想要的結果:

long phone = optUser.flatMap(User::getPhone).orElse(-1L);

flapMap 能夠將方法返回的各個流扁平化成爲一個流,具體在下一篇專門講流式數據處理的文章中細說。

  • 過濾:fliter

filiter,顧名思義是過濾的操做,咱們能夠將過濾操做作爲參數傳遞給該方法以實現過濾目的,假如咱們但願篩選 18 週歲以上的成年人,則能夠實現以下:

optUser.filter(u -> u.getAge() >= 18).ifPresent(u -> System.out.println("Adult:" + u));

1.3 默認行爲

默認行爲是當 Optional 在不知足條件時所執行的操做,好比在上面的例子中咱們使用的 orElse() 就是一個默認操做,用於在 Optional 對象爲空時執行特定操做,固然也有一些默認操做是當知足條件的對象存在時執行的操做。

  • get()

get 方法用於獲取變量的值,可是當變量不存在時則會拋出 NoSuchElementException,因此若是不肯定變量是否存在則不建議使用

  • orElse(T other)

當 Optional 的變量不知足給定條件時,則執行 orElse,好比前面當 str 爲 null 時返回 0。

  • orElseGet(Supplier<? extends X> expectionSupplier)

若是條件不成立時須要執行相對複雜的邏輯而不是簡單的返回操做,則可使用 orElseGet 實現:

long phone = optUser.map(User::getPhone).map(Optional::get).orElseGet(() -> {
    // do something here
    return -1L;
});
  • orElseThrow(Supplier<? extends X> expectionSupplier)

get() 方法相似,都是在不知足條件時返回異常,不過這裏咱們能夠指定返回的異常類型。

  • ifPresent(Consumer<? super T>)

當知足條件時執行傳入的參數化操做。

二. 注意事項

Optional 是一個 final 類且未實現任何接口,因此當咱們在利用該類包裝定義類的屬性的時候,若是咱們定義的類有序列化的需求,那麼由於 Optional 沒有實現 Serializable 接口,這個時候執行序列化操做就會有問題:

public class User implements Serializable {
    private long id;
    private String name;
    private int age;
    private Optional<Long> phone;  // 不能序列化
    private Optional<String> email;  // 不能序列化
}

不過咱們能夠採用以下替換策略 Optinal:

private long phone;

public Optional<Long> getPhone() {
    return Optional.ofNullable(this.phone);
}

看來 Optional 類在設計的時候就沒有考慮將它做爲類的字段使用。

最後提醒一點,Optional 好用但不能濫用,在設計一個接口方法時是否採起 Optional 類型返回須要斟酌,一味的使用會讓代碼變得比較囉嗦,反而破壞了代碼的簡潔性。


鑑於做者水平有限,文中難免有錯誤之處,歡迎批評指正
我的博客:www.zhenchao.org

相關文章
相關標籤/搜索