54

Java函数式编程之Optional

 4 years ago
source link: https://www.tuicool.com/articles/Uf6Fbuz
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

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<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() ,例如:

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 只是一个简化操作的工具,并不是银弹,有很多实质性的编码问题无法解决,例如箭头型代码问题。

这里提到箭头型代码,下面尝试用常规方法和 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

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK