前提
java.util.Optional
是JDK8中引入的类,它是JDK从著名的Java工具包Guava
中移植过来。本文编写的时候使用的是JDK11。Optional
是一个包含了NULL
值或者非NULL
值的对象容器,它常用作明确表明没有结果(其实明确表明存在结果也可以用Optional
表示)的方法返回类型,这样可以避免NULL
值带来的可能的异常(一般是NullPointerException
)。也就是说,一个方法的返回值类型是Optional
,则应该避免返回NULL
,而应该让返回值指向一个包含NULL
对象的Optional
实例。Optional
的出现为NULL
判断、过滤操作、映射操作等提供了函数式适配入口,它算是Java引入函数式编程的一个重要的里程碑。
本文新增一个Asciidoc
的预览模式,可以体验一下Spring
官方文档的感觉:
- Github Page:www.throwable.club/adoc/201908…
- Coding Page:throwable.coding.me/adoc/201908…
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()
,如果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())); 复制代码
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(); } } 复制代码
这个方法的功能是简单的过滤功能,容器持有对象value
非NULL
会做一次判断,决定返回自身实例还是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
本质是一个对象容器,它的特征如下:
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+。- 并不是所有人都热衷于函数式编程,因为它带来了便捷的同时转变了代码的阅读逻辑(有些人甚至会认为降低了代码的可读性)。
附件
- Github Page:www.throwable.club/2019/08/07/…
- Coding Page:throwable.coding.me/2019/08/07/…
- Markdown或Asciidoc文件:github.com/zjcscut/blo…
(本文完 c-2-d e-a-20190805 by throwable)