如何更好地使用Java 8的Optional
Java 8中的Optional<T>
是一個可以包含或不可以包含非空值的容器對象,在 Stream API中很多地方也都使用到了Optional。
java中非常討厭的一點就是nullpoint,碰到空指針就會出錯拋Exception,然後需要逐行檢查是哪個對象為空,帶來大量的不必要精力損耗,拋出NPE錯誤不是用戶操作的錯誤,而是開發人員的錯誤,應該被避免,那麽只能在每個方法中加入非空檢查,閱讀性和維護性都比較差。
如下面這個代碼的手工非空檢查:
public
void
addAddressToCustomer(Customer customer, Address newAddress){
if
( customer ==
null
|| newAddress ==
null
)
return
;
if
( customer.getAddresses() ==
null
){
customer.setAddresses (
new
ArrayList<>());
}
customer.addAddress(newAddress);
}
另外還有一些開發人員喜歡通過非空檢查來實現業務邏輯,空對象不應該用來決定系統的行為,它們是意外的Exceptional值,應當被看成是錯誤,而不是業務邏輯狀態。
當我們一個方法返回List集合時,應該總是返回一個空的List,而不是Null,這就允許調用者能夠遍歷它而不必檢查Null,否則就拋出NPE。
但是如果我們根據標識鍵ID查詢數據庫,沒有查到,需要返回一個空對象怎麽辦?有人建議拋出Exception,其實這不符合函數方法一進一出的原則,變成一個函數方法有兩個返回,一個是正常返回,一個出錯Exception,函數式編程範式告誡我們不要輕易拋Exception。
這時Java 8的Optional就發揮作用了,允許我們返回一個空的對象。
Optional<T>有方法 isPresent() 和 get() 是用來檢查其包含的對象是否為空或不是,然後返回它,如:
Optional<SomeType> someValue = someMethod();
if (someValue.isPresent()) { // check
someValue.get().someOtherMethod(); // retrieve and call
}
但是這種用法並不能體現Java 8的全部好處,你可以將Optional看成是需要使用某個T值的方法之間某種中間人或者協調者Mediator,而不只是一個普通對象的包裝器。
如果你有一個值返回類型T,你有一個方法需要使用這個值,那麽你可以讓 Optional<T> 處於中間,確保它們之間交互進行,而不必要人工幹預。
這樣,協調者Optional<T>能夠照顧T的值提供給你的方法作為輸入參數,在這種情況下,如果T是空,可以確保不會出錯,這樣在T值為空時也可以讓一切都正常運作,你也可以讓Optional<T>執行其他動作,如執行一段代碼塊等等,這樣它就實際上是語言機制的很好的補充。
下面這個案例涉及到Lambda表達式 方法引用,是將單詞流中第一個以"L"開始單詞取出,作為返回結果是一個Optional<String>。
使用ifPresent()
這個案例的代碼如下:
Stream<string> names = Stream.of("Lamurudu", "Okanbi", "Oduduwa");
Optional<string> longest = names
.filter(name -> name.startsWith("L"))
.findFirst();
longest.ifPresent(name -> {
String s = name.toUpperCase();
System.out.println("The longest name is "+ s);
});
這裏ifPresent() 是將一個Lambda表達式作為輸入,T值如果不為空將傳入這個lambda。那麽這個lambda將不為空的單詞轉為大寫輸出顯示。在前面names單詞流尋找結果中,有可能找不到開始字母為L的單詞,返回為空,也可能找到不為空,這兩種情況都傳入lambda中,無需我們打開盒子自己編寫代碼來判斷,它自動幫助我們完成了,無需人工幹預。
使用map()
如果你想從Optional<T>中返回一個值怎麽辦?使用 map(),如下:
Stream<string> names = Stream.of("Lamurudu", "Okanbi", "Oduduwa");
Optional<string> longest = names
.filter(name -> name.startsWith("L"))
.findFirst();
Optional<string> lNameInCaps = longest.map(String::toUpperCase);
使用Optional<T>的map方法能夠返回另外一個Optional,如上面的 LnameInCaps,因為傳入map()的參數值也許會導致一個空值。
使用orElse()
如果在T可能空時你需要一個值的話,那麽可以使用 orElse(),它能在T值存在的情況下返回這個值,否則返回輸入值。
Stream<String> names = Stream.of("Lamurudu", "Okanbi", "Oduduwa");
Optional<String> longest = names
.filter(name -> name.startsWith("Q"))
.findFirst();
String alternate = longest.orElse("Nimrod");
System.out.println(alternate); //prints out "Nimrod"
使用orElseGet()
orElseGet() 方法類似於orElse(),但是不是直接返回輸入參數,而是調用輸入參數,返回調用的結果,這個輸入參數通常是lambda:
Stream<String> names = Stream.of("Lamurudu", "Okanbi", "Oduduwa");
Optional<String> longest = names
.filter(name -> name.startsWith("Q"))
.findFirst();
String alternate = longest.orElseGet(() -> {
// perform some interesting code operation
// then return the alternate value.
return "Nimrod";
});
System.out.println(alternate);
使用 orElseThrow()
orElseThrow()是在當遭遇Null時,決定拋出哪個Exception時使用:
Stream<String> names = Stream.of("Lamurudu", "Okanbi", "Oduduwa");
Optional<String> longest = names
.filter(name -> name.startsWith("Q"))
.findFirst();
longest.orElseThrow(NoSuchElementStartingWithQException::new);
總結,你能創建下面三種類型的Optional<T>:
Optional<SomeType> getSomeValue() {
// 返回一個空的Optional類型;
return Optional.empty();
}
Optional<SomeType> getSomeValue() {
SomeType value = ...;
// 使用這個方法,值不可以為空,否則拋exception
return Optional.of(value);
}
Optional<SomeType> getSomeValue() {
SomeType value = ...;
// 使用這個方法,值可以為空,如果為空返回Optional.empty
return Optional.ofNullable(value);
// usage
Optional<SomeType> someType = getSomeValue();
如何更好地使用Java 8的Optional