JDK8新增了java.util.Optional
類別,在談到這個類別如何使用之前,必須先引用一下Java Collection
API及JSR166參與者之一Doug Lea的話:
“Null sucks."
圖靈獎得主、快速排序發明者Tony Hoare,在QCon London 2009主講《Null References: The Billion Dollar Mistake》場次時也談到null
:
“I call it my billion-dollar mistake."
null
的問題在於含糊而不明確,引發的各種問題從Java開發者經常在與NullPointerException
奮戰可見一般,我在(補救 null 的策略〉中談過:
null
的最根本問題在於語意含糊不清,雖然就字面來說,null
可以是「不存在」、「沒有」、「無」或「空」的概念,因此在應用時,總是令人感到模稜兩可,也就讓開發者有了各自解釋的空間,當開發者想到「嘿!這邊可以沒有東西…」就直接放個null
,或者是想到「嗯!沒什麼東西可以傳回…」,就不假思索地傳回個null
,然後使用者就總是忘了檢查null
,引發各種可能的錯誤。
由於null
的根本問題在於含糊而不明確,要避免使用null
的方式,就是確認過去使用null
的時機與目的,並使用明確的語義。在過去使用null
的情況中,開發者於方法中傳回null
,通常代表著客戶端必須檢查是否為null
,並在null
的情況下使用預設值,以便後續程式繼續執行。舉個例子來說:
public static void main(String[] args) {
String nickName = getNickName("Duke");
if (nickName == null) {
nickName = "Openhome Reader";
}
out.println(nickName);
}
static String getNickName(String name) {
Map<String, String> nickNames = new HashMap<>(); // 假裝的鍵值資料庫
nickNames.put("Justin", "caterpillar");
nickNames.put("Monica", "momor");
nickNames.put("Irene", "hamimi");
return nickNames.get(name); // 鍵不存在時會傳回null
}
在上面的程式中,如果呼叫getNickName()
時忘了檢查null
,那麼就會直接顯示null
,在這個簡單的例子中並不會怎樣,只是顯示結果令人困惑罷了,但如果後續的執行流程牽涉到至關重要的結果,程式快樂地繼續執行下去,錯誤可能到最後才會呈現發生。
那麼可將getNickName
修改使一定會傳回Optional<String>
實例,但絕不要傳回null
。Optional
的語義是它可能包含也可能不包括值,要建立Optional
實例有幾個靜態方式,使用of()
方法可以指定非null
值建立Optional
實例,使用empty()
方法可以建立不包裏值的Optional
實例。例如,可使用Optional
來改寫上頭的getNickName()
方法:
static Optional<String> getNickName(String name) {
Map<String, String> nickNames = new HashMap<>();
nickNames.put("Justin", "caterpillar");
nickNames.put("Monica", "momor");
nickNames.put("Irene", "hamimi");
String nickName = nickNames.get(name);
return nickName == null ? Optional.empty() : Optional.of(nickName);
}
因為呼叫getNickName()
時傳回的是Optional
型態的實例,語義上表示它包含也可能不包括值,客戶端就要意識必須進行檢查,如果不檢查就直接呼叫Optional
的get()
方法:
String nickName = getNickName("Duke").get();
out.println(nickName);
在Optional
沒有包含值的情況下,就會直接拋出java.util.NoSuchElementException
,這實現了速錯(Fail fast)的概念,這讓開發者可以立即發現錯誤,並瞭解到必須使用程式碼作些檢查,可能的方式之一像是:
Optional<String> nickOptional = getNickName("Duke");
String nickName = "Openhome Reader";
if(nickOptional.isPresent()) {
nickName = nickOptional.get();
}
out.println(nickName);
不過這看來有點囉嗦,一個比較好的方式可以使用orElse()
方法,指定值不存在時的替代值:
Optional<String> nickOptional = getNickName("Duke");
out.println(nickOptional.orElse("Openhome Reader"));
過去許多程式庫中使用了不少null
,這些程式庫無法說改就改,可使用Optional
的ofNullable()
來銜接程式庫中會傳回null
的方法,使用ofNullable()
方法時,若指定了非null
值就會呼叫of()
方法,指定了null
值就會呼叫empty()
方法。例如,先前的getNickName()
方法可以更簡潔地修改為:
static Optional<String> getNickName(String name) {
Map<String, String> nickNames = new HashMap<>();
nickNames.put("Justin", "caterpillar");
nickNames.put("Monica", "momor");
nickNames.put("Irene", "hamimi");
return Optional.ofNullable(nickNames.get(name));
}