Java8新特性(二) – Optional類

Java8 新特性之 Optional 類

 

圖片.png

摘要: Optional不是對null關鍵字的一種替代,而是對於null判定提供了一種更加優雅的實現

Java8新特性系列

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

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

一. 直觀感受

假設我們需要返回一個字符串的長度,如果不借助第三方工具類,我們需要調用str.length()方法:

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

如果採用Optional類,實現如下:

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

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

二. 基本使用

1.對象創建

創建空對象

Optional<String> optStr = Optional.empty();

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

創建對象:不允許爲空

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

Optional<String> optStr = Optional.of(str);  // 當str爲null的時候,將拋出NullPointException

創建對象:允許爲空

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

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

2.流式處理

流式處理也是java8給我們帶來的一個重量級新特性,讓我們對集合的操作變得更加簡潔和高效,下一篇關於java8新特性的文章,將對流失處理進行全面的講解。這裏Optional也提供了兩個基本的流失處理:映射和過濾。

爲了演示,我們設計了一個User類,如下:

/**
 * @author: zhenchao.Wang 2016-9-24 15:36:56
 */
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));

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;  // 不能序列化

不過我們可以採用如下替換策略:

private long phone;

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

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章