java.util.Optional
是JDK8中引入的類,它是JDK從著名的Java工具包Guava
中移植過來。本文編寫的時候使用的是JDK11。Optional
是一個包含了NULL
值或者非NULL
值的對象容器,它經常使用做明確代表沒有結果(其實明確代表存在結果也能夠用Optional
表示)的方法返回類型,這樣能夠避免NULL
值帶來的可能的異常(通常是NullPointerException
)。也就是說,一個方法的返回值類型是Optional
,則應該避免返回NULL
,而應該讓返回值指向一個包含NULL
對象的Optional
實例。Optional
的出現爲NULL
判斷、過濾操做、映射操做等提供了函數式適配入口,它算是Java引入函數式編程的一個重要的里程碑。java
本文新增一個Asciidoc
的預覽模式,能夠體驗一下Spring
官方文檔的感受:git
Optional
的源碼比較簡單,歸根於它是一個簡單的對象容器。下面會結合源碼分析它的全部構造、屬性、方法和對應的使用場景。github
Optional
的屬性和構造以下:編程
public final class Optional<T> {
// 這個是通用的表明NULL值的Optional實例
private static final Optional<?> EMPTY = new Optional<>();
// 泛型類型的對象實例
private final T value;
// 實例化Optional,注意是私有修飾符,value置爲NULL
private Optional() {
this.value = null;
}
// 直接返回內部的EMPTY實例
public static<T> Optional<T> empty() {
@SuppressWarnings("unchecked")
Optional<T> t = (Optional<T>) EMPTY;
return t;
}
// 經過value實例化Optional,若是value爲NULL則拋出NPE
private Optional(T value) {
this.value = Objects.requireNonNull(value);
}
// 經過value實例化Optional,若是value爲NULL則拋出NPE,實際上就是使用Optional(T value)
public static <T> Optional<T> of(T value) {
return new Optional<>(value);
}
// 若是value爲NULL則返回EMPTY實例,不然調用Optional#of(value)
public static <T> Optional<T> ofNullable(T value) {
return value == null ? empty() : of(value);
}
// 暫時省略其餘代碼
}
複製代碼
若是明確一個對象實例不爲NULL
的時候,應該使用Optional#of()
,例如:安全
Order o = selectByOrderId(orderId);
assert null != o
Optional op = Optional.of(o);
複製代碼
若是沒法明確一個對象實例是否爲NULL
的時候,應該使用Optional#ofNullable()
,例如:app
Optional op = Optional.ofNullable(selectByOrderId(orderId));
複製代碼
明確表示一個持有NULL
值的Optional
實例可使用Optional.empty()
。函數式編程
// 若是value爲空,則拋出NPE,不然直接返回value
public T get() {
if (value == null) {
throw new NoSuchElementException("No value present");
}
return value;
}
複製代碼
get()
方法通常是須要明確value
不爲NULL
的時候使用,它作了先驗value
的存在性。例如:函數
Order o = selectByOrderId(orderId);
assert null != o
Optional op = Optional.of(o);
Order value = op.get();
複製代碼
// 判斷value是否存在,不爲NULL則返回true,若是爲NULL則返回false
public boolean isPresent() {
return value != null;
}
複製代碼
舉個例子:工具
Order o = selectByOrderId(orderId);
boolean existed = Optional.ofNullable(o).isPresent();
複製代碼
isEmpty()
是JDK11引入的方法,是isPresent()
的反向判斷:源碼分析
// 判斷value是否存在,爲NULL則返回true,爲非NULL則返回false
public boolean isEmpty() {
return value == null;
}
複製代碼
ifPresent()
方法的做用是:若是value
不爲NULL
,則使用value
調用消費者函數式接口的消費方法Consumer#accept()
:
public void ifPresent(Consumer<? super T> action) {
if (value != null) {
action.accept(value);
}
}
複製代碼
例如:
Optional.ofNullable(selectByOrderId(orderId)).ifPresent(o-> LOGGER.info("訂單ID:{}",o.getOrderId());
複製代碼
ifPresentOrElse()
方法是JDK9新增的方法,它是ifPresent()
方法的增強版,若是value
不爲NULL
,則使用value
調用消費者函數式接口的消費方法Consumer#accept()
,若是value
爲NULL
則執行Runnable#run()
:
public void ifPresentOrElse(Consumer<? super T> action, Runnable emptyAction) {
if (value != null) {
action.accept(value);
} else {
emptyAction.run();
}
}
複製代碼
例如:
String orderId = "xxxx";
Optional.ofNullable(selectByOrderId(orderId)).ifPresentOrElse(o-> LOGGER.info("訂單ID:{}",o.getOrderId()), ()-> LOGGER.info("訂單{}不存在",o.getOrderId()));
複製代碼
public Optional<T> filter(Predicate<? super T> predicate) {
// 判斷predicate不能爲NULL
Objects.requireNonNull(predicate);
// value爲NULL,說明是空實例,則直接返回自身
if (!isPresent()) {
return this;
} else {
// value不爲NULL,則經過predicate判斷,命中返回自身,不命中則返回空實例empty
return predicate.test(value) ? this : empty();
}
}
複製代碼
這個方法的功能是簡單的過濾功能,容器持有對象value
非NULL
會作一次判斷,決定返回自身實例仍是empty()
。例如:
Optional.ofNullable(selectByOrderId(orderId)).filter(o -> o.getStatus() == 1).ifPresent(o-> LOGGER.info("訂單{}的狀態爲1",o.getOrderId));
複製代碼
map()
是簡單的值映射操做:
public <U> Optional<U> map(Function<? super T, ? extends U> mapper) {
// 判斷mapper不能爲NULL
Objects.requireNonNull(mapper);
// value爲NULL,說明是空實例,則直接返回empty()
if (!isPresent()) {
return empty();
} else {
// value不爲NULL,經過mapper轉換類型,從新封裝爲可空的Optional實例
return Optional.ofNullable(mapper.apply(value));
}
}
複製代碼
API註釋裏面的一個例子:
List<URI> uris = ...;
// 找到URI列表中未處理的URI對應的路徑
Optional<Path> p = uris.stream().filter(uri -> !isProcessedYet(uri)).findFirst().map(Paths::get);
複製代碼
flatMap()
方法也是一個映射操做,不過映射的Optional
類型返回值直接由外部決定,不須要經過值從新封裝爲Optional
實例:
public <U> Optional<U> flatMap(Function<? super T, ? extends Optional<? extends U>> mapper) {
// mapper存在性判斷
Objects.requireNonNull(mapper);
// value爲NULL,說明是空實例,則直接返回empty()
if (!isPresent()) {
return empty();
} else {
// value不爲NULL,經過mapper轉換,直接返回mapper的返回值,作一次空判斷
@SuppressWarnings("unchecked")
Optional<U> r = (Optional<U>) mapper.apply(value);
return Objects.requireNonNull(r);
}
}
複製代碼
例如:
class OptionalOrderFactory{
static Optional<Order> create(String id){
//省略...
}
}
String orderId = "xxx";
Optional<Order> op = Optional.of(orderId).flatMap(id -> OptionalOrderFactory.create(id));
複製代碼
public Optional<T> or(Supplier<? extends Optional<? extends T>> supplier) {
// supplier存在性判斷
Objects.requireNonNull(supplier);
// value不爲NULL,則直接返回自身
if (isPresent()) {
return this;
} else {
// value爲NULL,則返回supplier提供的Optional實例,作一次空判斷
@SuppressWarnings("unchecked")
Optional<T> r = (Optional<T>) supplier.get();
return Objects.requireNonNull(r);
}
}
複製代碼
例如:
Order a = null;
Order b = select();
// 拿到的就是b訂單實例包裝的Optional
Optional<Order> op = Optional.ofNullable(a).or(b);
複製代碼
// 對value作NULL判斷,轉換爲Stream類型
public Stream<T> stream() {
if (!isPresent()) {
return Stream.empty();
} else {
return Stream.of(value);
}
}
複製代碼
// 值不爲NULL則直接返回value,不然返回other
public T orElse(T other) {
return value != null ? value : other;
}
複製代碼
orElse()
就是常見的提供默認值兜底的方法,例如:
String v1 = null;
String v2 = "default";
// 拿到的就是v2對應的"default"值
String value = Optional.ofNullable(v1).orElse(v2);
複製代碼
// 值不爲NULL則直接返回value,不然返回Supplier#get()
public T orElseGet(Supplier<? extends T> supplier) {
return value != null ? value : supplier.get();
}
複製代碼
orElseGet()
只是orElse()
方法的升級版,例如:
String v1 = null;
Supplier<String> v2 = () -> "default";
// 拿到的就是v2對應的"default"值
String value = Optional.ofNullable(v1).orElseGet(v2);
複製代碼
// 若是值爲NULL,則拋出NoSuchElementException,不然直接返回value
public T orElseThrow() {
if (value == null) {
throw new NoSuchElementException("No value present");
}
return value;
}
// 若是值不爲NULL,則直接返回value,不然返回Supplier#get()提供的異常實例
public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
if (value != null) {
return value;
} else {
throw exceptionSupplier.get();
}
}
複製代碼
例如:
Optional.ofNullable(orderInfoVo.getAmount()).orElseThrow(()-> new IllegalArgumentException(String.format("%s訂單的amount不能爲NULL",orderInfoVo.getOrderId())));
複製代碼
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof Optional)) {
return false;
}
Optional<?> other = (Optional<?>) obj;
return Objects.equals(value, other.value);
}
public int hashCode() {
return Objects.hashCode(value);
}
複製代碼
這兩個方法都是比較value
,說明了Optional
實例若是使用於HashMap
的KEY,只要value
相同,對於HashMap
就是同一個KEY。如:
Map<Optional,Boolean> map = new HashMap<>();
Optional<String> op1 = Optional.of("throwable");
map.put(op1, true);
Optional<String> op2 = Optional.of("throwable");
map.put(op2, false);
// 輸出false
System.out.println(map.get(op1));
複製代碼
下面展現一下Optional
的一些常見的使用場景。
空判斷主要是用於不知道當前對象是否爲NULL
的時候,須要設置對象的屬性。不使用Optional
時候的代碼以下:
if(null != order){
order.setAmount(orderInfoVo.getAmount());
}
複製代碼
使用Optional
時候的代碼以下:
Optional.ofNullable(order).ifPresent(o -> o.setAmount(orderInfoVo.getAmount()));
// 若是判斷空的對象是OrderInfoVo以下
Order o = select();
OrderInfoVo vo = ...
Optional.ofNullable(vo).ifPresent(v -> o.setAmount(v.getAmount()));
複製代碼
使用Optional
實現空判斷的好處是只有一個屬性設值的時候能夠壓縮代碼爲一行,這樣作的話,代碼會相對簡潔。
在維護一些老舊的系統的時候,不少狀況下外部的傳參沒有作空判斷,所以須要寫一些斷言代碼如:
if (null == orderInfoVo.getAmount()){
throw new IllegalArgumentException(String.format("%s訂單的amount不能爲NULL",orderInfoVo.getOrderId()));
}
if (StringUtils.isBlank(orderInfoVo.getAddress()){
throw new IllegalArgumentException(String.format("%s訂單的address不能爲空",orderInfoVo.getOrderId()));
}
複製代碼
使用Optional
後的斷言代碼以下:
Optional.ofNullable(orderInfoVo.getAmount()).orElseThrow(()-> new IllegalArgumentException(String.format("%s訂單的amount不能爲NULL",orderInfoVo.getOrderId())));
Optional.ofNullable(orderInfoVo.getAddress()).orElseThrow(()-> new IllegalArgumentException(String.format("%s訂單的address不能爲空",orderInfoVo.getOrderId())));
複製代碼
下面是一個仿真案例,模擬的步驟以下:
@Data
static class Customer {
private Long id;
}
@Data
static class Order {
private Long id;
private String orderId;
private Long customerId;
}
@Data
static class OrderDto {
private String orderId;
}
// 模擬客戶查詢
private static List<Customer> selectCustomers(List<Long> ids) {
return null;
}
// 模擬訂單查詢
private static List<Order> selectOrders(List<Long> customerIds) {
return null;
}
// main方法
public static void main(String[] args) throws Exception {
List<Long> ids = new ArrayList<>();
List<OrderDto> view = Optional.ofNullable(selectCustomers(ids))
.filter(cs -> !cs.isEmpty())
.map(cs -> selectOrders(cs.stream().map(Customer::getId).collect(Collectors.toList())))
.map(orders -> {
List<OrderDto> dtoList = new ArrayList<>();
orders.forEach(o -> {
OrderDto dto = new OrderDto();
dto.setOrderId(o.getOrderId());
dtoList.add(dto);
});
return dtoList;
}).orElse(Collections.emptyList());
}
複製代碼
Optional
本質是一個對象容器,它的特徵以下:
Optional
做爲一個容器承載對象,提供方法適配部分函數式接口,結合部分函數式接口提供方法實現NULL
判斷、過濾操做、安全取值、映射操做等等。Optional
通常使用場景是用於方法返回值的包裝,固然也能夠做爲臨時變量從而享受函數式接口的便捷功能。Optional
只是一個簡化操做的工具,能夠解決多層嵌套代碼的節點空判斷問題(例如簡化箭頭型代碼)。Optional
並不是銀彈。這裏提到箭頭型代碼,下面嘗試用常規方法和Optional
分別解決:
// 假設VO有多個層級,每一個層級都不知道父節點是否爲NULL,以下
// - OrderInfoVo
// - UserInfoVo
// - AddressInfoVo
// - address(屬性)
// 假設我要爲address屬性賦值,那麼就會產生箭頭型代碼。
// 常規方法
String address = "xxx";
OrderInfoVo o = ...;
if(null != o){
UserInfoVo uiv = o.getUserInfoVo();
if (null != uiv){
AddressInfoVo aiv = uiv.getAddressInfoVo();
if (null != aiv){
aiv.setAddress(address);
}
}
}
// 使用Optional
String address = "xxx";
OrderInfoVo o = null;
Optional.ofNullable(o)
.map(OrderInfoVo::getUserInfoVo)
.map(UserInfoVo::getAddressInfoVo)
.ifPresent(a -> a.setAddress(address));
複製代碼
使用Optional
解決箭頭型代碼,經過映射操做map()
能減小大量的if
和NULL
判斷分支,使得代碼更加簡潔。
有些開發者提議把DAO
方法的返回值類型定義爲Optional
,筆者對此持中立態度,緣由是:
Optional
是JDK1.8引入,低版本的JDK並不能使用,不是全部的系統都能平滑遷移到JDK1.8+。(本文完 c-2-d e-a-20190805 by throwable)