Java8之熟透Optional
一、使用Optional引言
1.1、程式碼問題引出
在寫程式的時候一般都遇到過 NullPointerException
,所以經常會對程式進行非空的判斷:
User user = getUserById(id);
if (user != null) {
String username = user.getUsername();
System.out.println("Username is: " + username); // 使用 username
}
複製程式碼
為瞭解決這種尷尬的處境,JDK 終於在 Java8 的時候加入了 Optional
類,檢視 Optional
的 javadoc 介紹:
A container object which may or may not contain a non-null value. If a value is present,isPresent() will return true and get() will return the value.
複製程式碼
這是一個可以包含或者不包含非 null
值的容器。如果值存在則 isPresent()
方法會返回 true
,呼叫 get()
方法會返回該物件。
1.2、解決進階
我們假設 getUserById
已經是個客觀存在的不能改變的方法,那麼利用 isPresent
和 get
兩個方法,我們現在能寫出下面的程式碼:
Optional<User> user = Optional.ofNullable(getUserById(id));
if (user.isPresent()) {
String username = user.get().getUsername();
System.out.println("Username is: " + username); // 使用 username
}
複製程式碼
好像看著程式碼是優美了點,但是事實上這與之前判斷 null
值的程式碼沒有本質的區別,反而用 Optional
去封裝 value,增加了程式碼量。所以我們來看看 Optional
Optional
。
二、Optional三個靜態構造方法
1)概述:
JDK 提供三個靜態方法來構造一個 Optional
:
-
Optional.of(T value)
public static <T> Optional<T> of(T value) { return new Optional<>(value); } 複製程式碼
該方法通過一個非
null
的 value 來構造一個Optional
,返回的Optional
包含了 value 這個值。對於該方法,傳入的引數一定不能為null
,否則便會丟擲NullPointerException
。 -
Optional.ofNullable(T value)
public static <T> Optional<T> ofNullable(T value) { return value == null ? empty() : of(value); } 複製程式碼
該方法和
of
方法的區別在於,傳入的引數可以為null
—— 但是前面 javadoc 不是說Optional
只能包含非null
值嗎?我們可以看看ofNullable
方法的原始碼。原來該方法會判斷傳入的引數是否為
null
,如果為null
的話,返回的就是Optional.empty()
。 -
Optional.empty()
public static<T> Optional<T> empty() { @SuppressWarnings("unchecked") Optional<T> t = (Optional<T>) EMPTY; return t; } 複製程式碼
該方法用來構造一個空的
Optional
,即該Optional
中不包含值 —— 其實底層實現還是 如果 Optional 中的 value 為 null 則該 Optional 為不包含值的狀態,然後在 API 層面將Optional
表現的不能包含null
值,使得Optional
只存在 包含值 和 不包含值 兩種狀態。
2)分析:
前面 javadoc 也有提到,Optional
的 isPresent()
方法用來判斷是否包含值,get()
用來獲取 Optional
包含的值 —— 值得注意的是,如果值不存在,即在一個Optional.empty 上呼叫 get() 方法的話,將會丟擲 NoSuchElementException異常。
3)總結:
1)Optional.of(obj): 它要求傳入的 obj 不能是 null 值的,否則還沒開始進入角色就倒在了 NullPointerException 異常上了. 2)Optional.ofNullable(obj): 它以一種智慧的,寬容的方式來構造一個 Optional 例項. 來者不拒,傳 null 進到就得到 Optional.empty(),非 null 就呼叫 Optional.of(obj). 那是不是我們只要用 Optional.ofNullable(obj) 一勞永逸,以不變應二變的方式來構造 Optional 例項就行了呢? 那也未必,否則 Optional.of(obj) 何必如此暴露呢,私有則可。
三、Optional常用方法詳解
3.1、Optional常用方法概述
-
Optional.of(T t)
將指定值用 Optional 封裝之後返回,如果該值為 null,則丟擲一個 NullPointerException 異常。
-
Optional.empty()
建立一個空的 Optional 例項。
-
Optional.ofNullable(T t)
將指定值用 Optional 封裝之後返回,如果該值為 null,則返回一個空的 Optional 物件。
-
isPresent
如果值存在返回true,否則返回false
-
ifPresent
如果Optional例項有值則為其呼叫consumer,否則不做處理。 要理解ifPresent方法,首先需要了解Consumer類。簡答地說,Consumer類包含一個抽象方法。該抽象方法對傳入的值進行處理,但沒有返回值。 Java8支援不用介面直接通過lambda表示式傳入引數。 如果Optional例項有值,呼叫ifPresent()可以接受介面段或lambda表示式。
-
Optional.get()
如果該值存在,將該值用 Optional 封裝返回,否則丟擲一個 NoSuchElementException 異常。
-
orElse(T t)
如果呼叫物件包含值,返回該值,否則返回t。
-
orElseGet(Supplier s)
如果呼叫物件包含值,返回該值,否則返回 s 獲取的值。
-
orElseThrow()
它會在物件為空的時候丟擲異常。
-
map(Function f)
如果值存在,就對該值執行提供的 mapping 函式呼叫。
-
flatMap(Function mapper)
如果值存在,就對該值執行提供的mapping 函式呼叫,返回一個 Optional 型別的值,否則就返回一個空的 Optional 物件。
3.2、Optional常用方法詳解
3.2.1、ifPresent
public void ifPresent(Consumer<? super T> consumer) {
if (value != null)
consumer.accept(value);
}
複製程式碼
如果 Optional
中有值,則對該值呼叫 consumer.accept
,否則什麼也不做。
所以對於引言上的例子,我們可以修改為:
Optional<User> user = Optional.ofNullable(getUserById(id));
user.ifPresent(u -> System.out.println("Username is: " + u.getUsername()));
複製程式碼
3.2.2、orElse
public T orElse(T other) {
return value != null ? value : other;
}
複製程式碼
如果 Optional
中有值則將其返回,否則返回 orElse
方法傳入的引數。
User user = Optional
.ofNullable(getUserById(id))
.orElse(new User(0,"Unknown"));
System.out.println("Username is: " + user.getUsername());
複製程式碼
3.2.3、orElseGet
public T orElseGet(Supplier<? extends T> other) {
return value != null ? value : other.get();
}
複製程式碼
orElseGet
與 orElse
方法的區別在於,orElseGet
方法傳入的引數為一個 Supplier
介面的實現 —— 當 Optional
中有值的時候,返回值;當 Optional
中沒有值的時候,返回從該 Supplier
獲得的值。
User user = Optional
.ofNullable(getUserById(id))
.orElseGet(() -> new User(0,"Unknown"));
System.out.println("Username is: " + user.getUsername());
複製程式碼
3.2.4、orElseThrow
public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
if (value != null) {
return value;
} else {
throw exceptionSupplier.get();
}
}
複製程式碼
orElseThrow
與 orElse
方法的區別在於,orElseThrow
方法當 Optional
中有值的時候,返回值;沒有值的時候會丟擲異常,丟擲的異常由傳入的 exceptionSupplier 提供。
舉例說明:
在 SpringMVC 的控制器中,我們可以配置統一處理各種異常。查詢某個實體時,如果資料庫中有對應的記錄便返回該記錄,否則就可以丟擲 EntityNotFoundException
,處理 EntityNotFoundException
的方法中我們就給客戶端返回Http 狀態碼 404 和異常對應的資訊 —— orElseThrow
完美的適用於這種場景。
@RequestMapping("/{id}")
public User getUser(@PathVariable Integer id) {
Optional<User> user = userService.getUserById(id);
return user.orElseThrow(() -> new EntityNotFoundException("id 為 " + id + " 的使用者不存在"));
}
@ExceptionHandler(EntityNotFoundException.class)
public ResponseEntity<String> handleException(EntityNotFoundException ex) {
return new ResponseEntity<>(ex.getMessage(),HttpStatus.NOT_FOUND);
}
複製程式碼
3.2.5、map
public<U> Optional<U> map(Function<? super T,? extends U> mapper) {
Objects.requireNonNull(mapper);
if (!isPresent())
return empty();
else {
return Optional.ofNullable(mapper.apply(value));
}
}
複製程式碼
如果當前 Optional
為 Optional.empty
,則依舊返回 Optional.empty
;否則返回一個新的 Optional
,該 Optional
包含的是:函式 mapper 在以 value 作為輸入時的輸出值。
String username = Optional.ofNullable(getUserById(id))
.map(user -> user.getUsername())
.orElse("Unknown")
.ifPresent(name -> System.out.println("Username is: " + name));
複製程式碼
而且我們可以多次使用 map
操作:
Optional<String> username = Optional.ofNullable(getUserById(id))
.map(user -> user.getUsername())
.map(name -> name.toLowerCase())
.map(name -> name.replace('_',' '))
.orElse("Unknown")
.ifPresent(name -> System.out.println("Username is: " + name));
複製程式碼
3.2.6、flatMap
public<U> Optional<U> flatMap(Function<? super T,Optional<U>> mapper) {
Objects.requireNonNull(mapper);
if (!isPresent())
return empty();
else {
return Objects.requireNonNull(mapper.apply(value));
}
}
複製程式碼
flatMap
方法與 map
方法的區別在於,map
方法引數中的函式 mapper
輸出的是值,然後 map
方法會使用 Optional.ofNullable
將其包裝為 Optional
;而 flatMap
要求引數中的函式 mapper
輸出的就是 Optional
。
Optional<String> username = Optional.ofNullable(getUserById(id))
.flatMap(user -> Optional.of(user.getUsername()))
.flatMap(name -> Optional.of(name.toLowerCase()))
.orElse("Unknown")
.ifPresent(name -> System.out.println("Username is: " + name));
複製程式碼
3.2.7、filter
public Optional<T> filter(Predicate<? super T> predicate) {
Objects.requireNonNull(predicate);
if (!isPresent())
return this;
else
return predicate.test(value) ? this : empty();
}
複製程式碼
filter
方法接受一個 Predicate
來對 Optional
中包含的值進行過濾,如果包含的值滿足條件,那麼還是返回這個 Optional
;否則返回 Optional.empty
。
Optional<String> username = Optional.ofNullable(getUserById(id))
.filter(user -> user.getId() < 10)
.map(user -> user.getUsername());
.orElse("Unknown")
.ifPresent(name -> System.out.println("Username is: " + name));
複製程式碼
四、Optional使用示例
4.1、使用展示一
當 user.isPresent() 為真,獲得它關聯的 orders的對映集合,為假則返回一個空集合時,我們用上面的 orElse,orElseGet 方法都乏力時,那原本就是 map 函式的責任,我們可以這樣一行:
return user.map(u -> u.getOrders()).orElse(Collections.emptyList())
//上面避免了我們類似 Java 8 之前的做法
if(user.isPresent()) {
return user.get().getOrders();
} else {
return Collections.emptyList();
}
複製程式碼
map 是可能無限級聯的,比如再深一層,獲得使用者名稱的大寫形式:
return user.map(u -> u.getUsername())
.map(name -> name.toUpperCase())
.orElse(null);
複製程式碼
以前的做法:
User user = .....
if(user != null) {
String name = user.getUsername();
if(name != null) {
return name.toUpperCase();
} else {
return null;
}
} else {
return null;
}
複製程式碼
filter() :如果有值並且滿足條件返回包含該值的Optional,否則返回空Optional。
Optional<String> longName = name.filter((value) -> value.length() > 6);
System.out.println(longName.orElse("The name is less than 6 characters"));
複製程式碼