Optional API
September 29, 2022先引用一下 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 怎麼了?
null
引發的各種問題從 Java 開發者經常在與 NullPointerException
奮戰可見一般,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
,在這個簡單的例子中並不會怎樣,只是顯示結果令人困惑罷了,但如果後續的執行流程牽涉到至關重要的結果,程式快樂地繼續執行下去,錯誤可能到最後才會呈現發生。
使用 Optional
那麼可將 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));
}