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官方文档的感觉
Github Page www.throwable.club/adoc/201908…Coding Page throwable.coding.me/adoc/201908…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()。
// 如果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()方法
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()))); 复制代码
下面是一个仿真案例 模拟的步骤如下
给出客户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 。并不是所有人都热衷于函数式编程 因为它带来了便捷的同时转变了代码的阅读逻辑 有些人甚至会认为降低了代码的可读性 。本文完 c-2-d e-a-20190805 by throwable
Java基础之Optional类(JDK1.8新特性) Optional是一个容器,它可以保存类型T的值,或者仅仅保存null,Optional类主要是用来避免空指针异常(NPE),其提供的一系列的方法配合Lambda表达式可以让代码更加清晰,语义化,以及避免了空指针异常的问题,这里要注意是避免空指针异常,而不是避免返回null。
Java Optional 在日常开发中,NPE异常是导致应用程序失败的最常见原因。为了解决空指针异常,Google公司知名的 Guava项目引入了Optional类,它通过使用检查空值的方式来防止代码污染,它鼓励程序员写更干净的代码。
Java 8新特性之Optional 在进行日常Java开发中遇见NullPointerException已经算是家常便饭了,有些情况没有考虑到有可能出现空指针异常,尤其是新手,即使考虑到了也要使用if-else去判断是否为空,这样有时候会让代码看上去复杂一些. 现在Java8有了Optional之后,空指针的校验就变得非常的方便和简洁,下面我们就来看看Optional的用法.
Java 8 新特性 Optional 类学习,理解并应用。NullPointerException空值检测 JDK1.8开始引入的特性,Optional 类主要解决空指针异常(NullPointerException)问题。 Optional类是一个可能包含或不包含非空值(可以为null)的容器对象。 如果一个值存在,调用 isPresent()方法将返回true、get()方法将返回该对象。 Optional类提供判断空值的方法,使用其中方法可以不用再显式地进行空值检测。
相关文章
- java 技术架构 for web
- Java中Vector与ArrayList的差别具体解释
- Java函数式编程和Lambda表达式
- 编程体系结构(03):Java集合容器
- Java并发编程(08):Executor线程池框架
- Java并发编程(01):线程的创建方式,状态周期管理
- Java并发编程【1.2时代】
- 遇见AI,从Java到数据挖掘。
- Java联网3D坦克大战(网络编程)
- 《21天学通Java(第6版)》—— 第 2 章 Java编程基础
- Java并发编程:CountDownLatch、CyclicBarrier和Semaphore
- Java并发编程:Thread类的使用介绍
- 在Java中实现UDP协议编程(DatagramSocket/DatagramPacket)
- java 网络编程之UDP通信和简单的群聊程序
- Java悲观锁和乐观锁
- Java_Servlet 中文乱码问题及解决方案剖析
- Java学习笔记_2 @IDEA软件使用及编程
- Java測试覆盖率工具----Cobertura,EclEmma
- Java开发 - 分页查询初体验
- 【JAVA并发编程专题】死锁的修复和定位
- kmp java implement--转