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
參考資料:
- Java 8 新特性概述
- Java 8 New API Tips
- Java 8 Optional, Revisited
- Lambda Expressions
- Java SE 8: Lambda Quick Start
- Java 8 Optional - Avoid Null and NullPointerException Altogether - and Keep It Pretty
本文網址:http://blog.tonycube.com/2015/10/java-java8-4-optional.html
由 Tony Blog 撰寫,請勿全文複製,轉載時請註明出處及連結,謝謝 😀
由 Tony Blog 撰寫,請勿全文複製,轉載時請註明出處及連結,謝謝 😀
好文~ 推
回覆刪除