zl程序教程

您现在的位置是:首页 >  后端

当前栏目

Java函数式编程之Optional

JAVA编程 函数 optional
2023-09-27 14:25:56 时间
java.util.Optional是JDK8中引入的类,它是JDK从著名的Java工具包Guava中移植过来。本文编写的时候使用的是JDK11。Optional是一个包含了NULL值或者非NULL值的对象容器,它常用作明确表明没有结果(其实明确表明存在结果也可以用Optional表示)的方法返回类型,这样可以避免NULL值带来的可能的异常(一般是NullPointerException)。
前提


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



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类提供判断空值的方法,使用其中方法可以不用再显式地进行空值检测。