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> 實例,但絕不要傳回 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));
}

分享到 LinkedIn 分享到 Facebook 分享到 Twitter