Java8 新功能筆記 (4) - Optional

Optional

Optional 是值的容器,只有兩種狀態,不是有值就是沒值。目的是做為 null 的替代方案。Optional 提供工廠方法,將你輸入的值產生為 Optional 物件,這時Optional 物件即為該值的容器,若要取回該值,必須使用 get() 方法。
Optional 屬於 value-based,對於識別敏感的操作(包含參考相等的判斷(==)、hash code或同步(synchronization)等)會有不確定性的結果,應該要避免使用這些操作。

將值轉為 Optional 的方法

  • of():接受非 null 的值並回傳 Optional 物件。
  • ofNullable():可以接受 null 的值,回傳 Optional 物件。

取得放在 Optional 物件內的值的方法

  • get():如果值存在就回傳這個值,否則就丟出 NoSuchElementException。
  • orElse(T other):如果值存在就回傳這個值,否則回傳 other 。
  • orElseGet(Supplier<? extends T> other):如果值存在就回傳這個值,否則就呼叫 other 並回傳它的結果。
  • orElseThrow(Supplier<? extends X> exceptionSupplier):如果值存在就回傳這個值,否則就丟出由 exceptionSupplier 建立的例外。

檢查值是否存在

  • boolean isPresent():如果值存在,回傳 true;不存在則回傳 false。
  • void ifPresent(Consumer<? super T> consumer):如果值存在,呼叫指定的 consumer 物件並將值傳給它處理;不存在則什麼都不做。

從範例學怎麼用 Optional

範例1:值不是null的情況

private void nameNotNull() {
    String name = "Tony";
    Optional<String> optName = Optional.of(name);
    System.out.println(optName.get());
}
原本的字串 name 透過 Optional.of() 方法包裝成 Optional 物件,然後用 get() 方法取出,在 name 不是 null 的情況,一切正常。 值不是 null 的情況沒什麼好處理的,因此之後的情況都會著重在怎麼處理 null 發生時。

範例2:同範例1但值變成 null 了

private void nameNull() {
    String name = null;
    Optional<String> optName = Optional.of(name);//of method make NPE Boom
    System.out.println(optName.get());
}
因為 of() 方法不接受 null,所以在產生 Optional 物件時,就發生錯誤了,而且是大魔王 NullPointerException。Optional 不是設計用來處理 null 的嗎?怎麼不行。這裡要改用另一個 ofNullable() 方法,繼續看下去。

範例3:改用ofNullable()

private void nameNullable() {
    String name = null;
    Optional<String> optName = Optional.ofNullable(name);
    System.out.println(optName.get());//get method make NoSuchElementException: No value present
}
可以了,null 值一樣可以產生 Optional 物件。BUT!在取值時出現例外,好險不是大魔王而是 NoSuchElementException。ofNullable 雖然可以產生 Optional 物件,可是因為原本的值是 null,所以雖然包裝成 Optional 依然是沒有值的狀態,對它取值當然就出現找不到的情況。 那這樣到底要 Optional 幹嘛?

範例4:使用前先檢查

private void nameIsPresent() {
    String name = null;
    Optional<String> optName = Optional.ofNullable(name);
    if (optName.isPresent()) {
        System.out.println(optName.get());
    } else {
        System.out.println("Name is null.");
    }
}
用 of() 的情況是絕對不能有 null,ofNullable() 則允許 null,它們最終都是將值包裝成 Optional 物件。我們可以使用 isPresent() 方法來確認是不是有值,這裡的動作和我們常寫的 if(name != null) 其實作用是相同的。檢查後再取值就萬無一失了,不過看起來好像換湯不換藥。 !重要守則:在每次呼叫 get() 前,先呼叫 isPresent() 確保,或改用 orElse()。

範例5:包裝值的另一個方式

private void nameEmpty() {
    String name = null;
    Optional<String> optName = (name == null) ? Optional.empty() : Optional.of(name);
    System.out.println(optName.get());//get method make NoSuchElementException: No value present
}
我們可以使用三元運算子來判斷,如果是 null 就回傳 Optional.empty(),有值就用 of()。這樣可以避掉丟 null 給 of() 的風險,同時也回傳 Optional 提供的 empty(),表示為無值。因為是無值,所以用 get() 一樣會丟出例外,還是要用 isPresent() 確認。

範例6:使用 orElse 就可以丟掉 if 了

private void nameOrElse() {
    String name = null;
    Optional<String> optName = Optional.ofNullable(name);
    System.out.println(optName.orElse("Name is null."));
}
orElse() 會做兩件事,先用 isPresent() 確認有無值,有值就直接 get() 返回值;否則就把參數中的值回傳。這樣就可以把原本 5 行的 if 判斷句縮減成一行。

範例7:使用 orElseGet

private void nameOrElseGet() {
    String name = null;
    Optional<String> optName = Optional.ofNullable(name);
    System.out.println(optName.orElseGet(() -> "WHAT! null!"));//lambda
} 
如果你要在無值時多做一些事,可以改用 orElseGet 來做,它的參數是 Supplier 介面,這裡使用 lambda expression 來簡化程式碼。

範例8:使用 OrElseThrow 丟出例外

private void nameOrElseThrow() {
    class MyException extends Exception {
        public MyException(String message) {
            super(message);
        }
    }

    String name = null;
    Optional<String> optName = Optional.ofNullable(name);
    try {
        System.out.println(optName.orElseThrow(() -> new MyException("WHAT! NULL!")));
    } catch (MyException e){
        System.out.println("MY EXCEPTION! " + e.getMessage());
    }
}
如果你想在無值時丟出例外,就使用 OrElseThrow。

範例:模擬使用情境

class User {
    public int id;
    public String name;
    public User(int id, String name) {
        this.id = id;
        this.name = name;
    }
}

//==== 不使用 Optional ====
private List<User> getUsers() {
    //假裝從資料庫取值
    List<User> users = asList(new User(1, "Tony"),
            new User(2, "John"),
            new User(3, "Emma"));
    return users;//有可能 null
}

private User findUserByName(String name) {
    List<User> users = getUsers();
    if (users != null) {//很好,有檢查 null
        for (User u : users) {
            if (u.name.equals(name)) {
                return u;
            }
        }
    }
    return null;//可能找不到而返回 null
}

private void optionalDemo() {
    User user = findUserByName("Amy");
    if (user != null) {//很好,有檢查 null
        System.out.println(user.name + " id is " + user.id);
    } else {
        System.out.println("User not found.");
    }
}

//==== 使用 Optional ====
private Optional<List<User>> getUsers2() {
    //假裝從資料庫取值
    List<User> users = asList(new User(1, "Tony"),
            new User(2, "John"),
            new User(3, "Emma"));
    return Optional.ofNullable(users);
}

private Optional<User> findUserByName2(String name) {
    Optional<List<User>> users = getUsers2();
    //對要使用的資料檢查,參數也要
    if (users.isPresent() &&
            Optional.ofNullable(name).isPresent()) {
            //使用 stream 取代 foreach 迴圈
            //findFirst()回傳的是 Optional
        return users.get().stream().filter(u -> u.name.equals(name)).findFirst();
    }
    return Optional.empty();
}

private void optionalDemo2() {
    Optional<User> user = findUserByName2("Amy");
    //使用 isPresent() 有點冗長
    if (user.isPresent()) {
        System.out.println(user.get().name + " id is " + user.get().id);
    } else {
        System.out.println("User not found.");
    }

    //改用 ifPresent 只要一行,但是無值時不做任何回應
    user.ifPresent(u -> System.out.println(u.name + " id is " + u.id));
}
消滅 Null 大作戰。持續戰鬥~

幾個值得注意的 Java 8 API

參考資料:

本文網址:https://blog.tonycube.com/2015/10/java-java8-4-optional.html
Tony Blog 撰寫,請勿全文複製,轉載時請註明出處及連結,謝謝 😀

1 則留言

留言小提醒:
1.回覆時間通常在晚上,如果太忙可能要等幾天。
2.請先瀏覽一下其他人的留言,也許有人問過同樣的問題。
3.程式碼請先將它編碼後再貼上。(線上編碼:http://bit.ly/1DL6yog)
4.文字請加上標點符號及斷行,難以閱讀者恕難回覆。
5.感謝您的留言,您的問題也可能幫助到其他有相同問題的人。