Java函数式编程之Optional

java.util.Optional
是JDK8中引入的类,它是JDK从著名的Java工具包 Guava
中移植过来。本文编写的时候使用的是JDK11。 Optional
是一个包含了 NULL
值或者非 NULL
值的对象容器,它常用作明确表明没有结果(其实明确表明存在结果也可以用 Optional
表示)的方法返回类型,这样可以避免 NULL
值带来的可能的异常(一般是 NullPointerException
)。也就是说,一个方法的返回值类型是 Optional
,则应该避免返回 NULL
,而应该让返回值指向一个包含 NULL
对象的 Optional
实例。 Optional
的出现为 NULL
判断、过滤操作、映射操作等提供了函数式适配入口,它算是Java引入函数式编程的一个重要的里程碑。

本文新增一个 Asciidoc
的预览模式,可以体验一下 Spring
官方文档的感觉:

Optional各个方法源码分析和使用场景

Optional
的源码比较简单,归根于它是一个简单的对象容器。下面会结合源码分析它的所有构造、属性、方法和对应的使用场景。

Optional属性和构造

Optional
的属性和构造如下:

public final class Optional {

    // 这个是通用的代表NULL值的Optional实例
    private static final Optional EMPTY = new Optional();

    // 泛型类型的对象实例
    private final T value;
    
    // 实例化Optional,注意是私有修饰符,value置为NULL
    private Optional() {
        this.value = null;
    }
    
    // 直接返回内部的EMPTY实例
    public static Optional empty() {
        @SuppressWarnings("unchecked")
        Optional t = (Optional) 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  Optional of(T value) {
        return new Optional(value);
    }

    // 如果value为NULL则返回EMPTY实例,否则调用Optional#of(value)
    public static  Optional 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()
,例如:

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 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()
,如果 value
NULL
则执行 Runnable#run()

public void ifPresentOrElse(Consumer 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 filter(Predicate 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()方法

map()
是简单的值映射操作:

public  Optional map(Function 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 uris = ...;
// 找到URI列表中未处理的URI对应的路径
Optional p = uris.stream().filter(uri -> !isProcessedYet(uri)).findFirst().map(Paths::get);

flatMap()方法

flatMap()
方法也是一个映射操作,不过映射的 Optional
类型返回值直接由外部决定,不需要通过值重新封装为 Optional
实例:

public  Optional flatMap(Function<? super T, ? extends Optional> mapper) {
    // mapper存在性判断
    Objects.requireNonNull(mapper);
    // value为NULL,说明是空实例,则直接返回empty()
    if (!isPresent()) {
        return empty();
    } else {
        // value不为NULL,通过mapper转换,直接返回mapper的返回值,做一次空判断
        @SuppressWarnings("unchecked")
        Optional r = (Optional) mapper.apply(value);
        return Objects.requireNonNull(r);
    }
}

例如:

class OptionalOrderFactory{

    static Optional create(String id){
        //省略...
    }
}

String orderId = "xxx";
Optional op =  Optional.of(orderId).flatMap(id -> OptionalOrderFactory.create(id));

or()方法

public Optional or(Supplier<? extends Optional> supplier) {
    // supplier存在性判断
    Objects.requireNonNull(supplier);
    // value不为NULL,则直接返回自身
    if (isPresent()) {
        return this;
    } else {
        // value为NULL,则返回supplier提供的Optional实例,做一次空判断
        @SuppressWarnings("unchecked")
        Optional r = (Optional) supplier.get();
        return Objects.requireNonNull(r);
    }
}

例如:

Order a = null;
Order b = select();
// 拿到的就是b订单实例包装的Optional
Optional op = Optional.ofNullable(a).or(b);

stream()方法

// 对value做NULL判断,转换为Stream类型
public Stream 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 supplier) {
    return value != null ? value : supplier.get();
}

orElseGet()
只是 orElse()
方法的升级版,例如:

String v1 = null;
Supplier 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  T orElseThrow(Supplier 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 map = new HashMap();
Optional op1 = Optional.of("throwable");
map.put(op1, true);
Optional 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 selectCustomers(List ids) {
    return null;
}

// 模拟订单查询
private static List selectOrders(List customerIds) {
    return null;
}

// main方法
public static void main(String[] args) throws Exception {
    List ids = new ArrayList();
    List view = Optional.ofNullable(selectCustomers(ids))
            .filter(cs -> !cs.isEmpty())
            .map(cs -> selectOrders(cs.stream().map(Customer::getId).collect(Collectors.toList())))
            .map(orders -> {
                List 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
    只是一个简化操作的工具,并不是银弹,有很多实质性的编码问题无法解决,例如箭头型代码问题。

这里提到箭头型代码,下面尝试用常规方法和 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 = ...;
Optional.ofNullable(o).ifPresent(oiv-> {
      Optional.ofNullable(oiv.getUserInfoVo()).ifPresent(uiv -> {
           Optional.ofNullable(uiv.getAddressInfoVo()).ifPresent(aiv->{
                aiv.setAddress(address);
           })
      })
});

有些开发者提议把 DAO
方法的返回值类型定义为 Optional
,笔者对此持中立态度,原因是:

Optional