Java8新特性系列
- Java8新特性(一) - lambda表達式
- Java8新特性(二) - Optional類
- Java8新特性(三) - 流式資料處理
- Java8新特性(四) - 預設接口方法
NullPointException可以說是所有java程式員都遇到過的一個異常,雖然java從設計之初就力圖讓程式員脫離指針的苦海,但是指針确實是實際存在的,而java設計者也隻能是讓指針在java語言中變得更加簡單、易用,而不能完全的将其剔除,是以才有了我們日常所見到的關鍵字
null
。
空指針異常是一個運作時異常,對于這一類異常,如果沒有明确的處理政策,那麼最佳實踐在于讓程式早點挂掉,但是很多場景下,不是開發人員沒有具體的處理政策,而是根本沒有意識到空指針異常的存在。當異常真的發生的時候,處理政策也很簡單,在存在異常的地方添加一個if語句判定即可,但是這樣的應對政策會讓我們的程式出現越來越多的null判定,我們知道一個良好的程式設計,應該讓代碼中盡量少出現null關鍵字,而java8所提供的
Optional
類則在減少NullPointException的同時,也提升了代碼的美觀度。但首先我們需要明确的是,它并 不是對
null
關鍵字的一種替代,而是對于null判定提供了一種更加優雅的實作,進而避免NullPointException。
一. 直覺感受
假設我們需要傳回一個字元串的長度,如果不借助第三方工具類,我們需要調用
str.length()
方法:
if(null == str) { // 空指針判定
return ;
}
return str.length();
如果采用Optional類,實作如下:
return Optional.ofNullable(str).map(String::length).orElse();
Optional的代碼相對更加簡潔,當代碼量較大時,我們很容易忘記進行null判定,但是使用Optional類則會避免這類問題。
二. 基本使用
1.對象建立
- 建立空對象
Optional<String> optStr = Optional.empty();
上面的示例代碼調用
empty()
方法建立了一個空的
Optional<String>
對象型。
- 建立對象:不允許為空
Optional提供了方法
of()
用于建立非空對象,該方法要求傳入的參數不能為空,否則抛
NullPointException
,示例如下:
- 建立對象:允許為空
如果不能确定傳入的參數是否存在null值的可能性,則可以用Optional的
ofNullable()
方法建立對象,如果入參為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();
其實這個時候,更好的方式是利用flatMap,一步拿到我們想要的結果:
long phone = optUser.flatMap(User::getPhone).orElse();
flapMap可以将方法傳回的各個流扁平化成為一個流,具體在下一篇專門講流式處理的文章中細說。
- 過濾:fliter
filiter,顧名思義是過濾的操作,我們可以将過濾操作做為參數傳遞給該方法,進而實作過濾目的,加入我們希望篩選18周歲以上的成年人,則可以實作如下:
optUser.filter(u -> u.getAge() >= ).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 -;
});
- 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在設計的時候就沒有考慮将它作為類的字段使用~