Java函數式編程之Optional

前提

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各個方法源碼分析和使用場景

Optional的源碼比較簡單,歸根於它是一個簡單的對象容器。下面會結合源碼分析它的全部構造、屬性、方法和對應的使用場景。github

Optional屬性和構造

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()函數式編程

get()方法

// 若是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();
複製代碼

isPresent()方法

// 判斷value是否存在,不爲NULL則返回true,若是爲NULL則返回false
public boolean isPresent() {
    return value != null;
}
複製代碼

舉個例子:工具

Order o = selectByOrderId(orderId);
boolean existed = Optional.ofNullable(o).isPresent();
複製代碼

isEmpty()方法

isEmpty()是JDK11引入的方法,是isPresent()的反向判斷:源碼分析

// 判斷value是否存在,爲NULL則返回true,爲非NULL則返回false
public boolean isEmpty() {
    return value == null;
}
複製代碼

ifPresent()方法

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()方法

ifPresentOrElse()方法是JDK9新增的方法,它是ifPresent()方法的增強版,若是value不爲NULL,則使用value調用消費者函數式接口的消費方法Consumer#accept(),若是valueNULL則執行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()));
複製代碼

filter()方法

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();
    }
}
複製代碼

這個方法的功能是簡單的過濾功能,容器持有對象valueNULL會作一次判斷,決定返回自身實例仍是empty()。例如:

Optional.ofNullable(selectByOrderId(orderId)).filter(o -> o.getStatus() == 1).ifPresent(o-> LOGGER.info("訂單{}的狀態爲1",o.getOrderId));
複製代碼

map()方法

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()方法

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));
複製代碼

or()方法

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);
複製代碼

stream()方法

// 對value作NULL判斷,轉換爲Stream類型
public Stream<T> stream() {
    if (!isPresent()) {
        return Stream.empty();
    } else {
        return Stream.of(value);
    }
}
複製代碼

orElse()方法

// 值不爲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);
複製代碼

orElseGet()方法

// 值不爲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);
複製代碼

orElseThrow()方法

// 若是值爲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())));
複製代碼

equals()和hashCode()方法

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實戰

下面展現一下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())));
複製代碼

綜合仿真案例

下面是一個仿真案例,模擬的步驟以下:

  • 給出客戶ID列表查詢客戶列表。
  • 基於存在的客戶列表中的客戶ID查詢訂單列表。
  • 基於訂單列表轉換爲訂單DTO視圖列表。
@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本質是一個對象容器,它的特徵以下:

  1. Optional做爲一個容器承載對象,提供方法適配部分函數式接口,結合部分函數式接口提供方法實現NULL判斷、過濾操做、安全取值、映射操做等等。
  2. Optional通常使用場景是用於方法返回值的包裝,固然也能夠做爲臨時變量從而享受函數式接口的便捷功能。
  3. Optional只是一個簡化操做的工具,能夠解決多層嵌套代碼的節點空判斷問題(例如簡化箭頭型代碼)。
  4. 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()能減小大量的ifNULL判斷分支,使得代碼更加簡潔。

有些開發者提議把DAO方法的返回值類型定義爲Optional,筆者對此持中立態度,緣由是:

  1. Optional是JDK1.8引入,低版本的JDK並不能使用,不是全部的系統都能平滑遷移到JDK1.8+。
  2. 並非全部人都熱衷於函數式編程,由於它帶來了便捷的同時轉變了代碼的閱讀邏輯(有些人甚至會認爲下降了代碼的可讀性)。

附件

(本文完 c-2-d e-a-20190805 by throwable)

相關文章
相關標籤/搜索