使用 Optional 取代 null


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>實例,但絕不要傳回nullOptional的語義是它可能包含也可能不包括值,要建立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型態的實例,語義上表示它包含也可能不包括值,客戶端就要意識必須進行檢查,如果不檢查就直接呼叫Optionalget()方法:

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,這些程式庫無法說改就改,可使用OptionalofNullable()來銜接程式庫中會傳回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));
    }