Java函数式编程之Optional

简介: 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官方文档的感觉:


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只是一个简化操作的工具,可以解决多层嵌套代码的节点空判断问题(例如简化箭头型代码)。
  4. 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()能减少大量的ifNULL判断分支,使得代码更加简洁。


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

  1. Optional是JDK1.8引入,低版本的JDK并不能使用,不是所有的系统都能平滑迁移到JDK1.8+。
  2. 并不是所有人都热衷于函数式编程,因为它带来了便捷的同时转变了代码的阅读逻辑(有些人甚至会认为降低了代码的可读性)。


附件



(本文完 c-2-d e-a-20190805 by throwable)


相关文章
|
2月前
|
安全 Java API
告别繁琐编码,拥抱Java 8新特性:Stream API与Optional类助你高效编程,成就卓越开发者!
【8月更文挑战第29天】Java 8为开发者引入了多项新特性,其中Stream API和Optional类尤其值得关注。Stream API对集合操作进行了高级抽象,支持声明式的数据处理,避免了显式循环代码的编写;而Optional类则作为非空值的容器,有效减少了空指针异常的风险。通过几个实战示例,我们展示了如何利用Stream API进行过滤与转换操作,以及如何借助Optional类安全地处理可能为null的数据,从而使代码更加简洁和健壮。
72 0
|
2月前
|
Java 程序员 API
解锁Java新纪元:Lambda表达式——让你的代码秒变高大上,函数式编程不再是梦!
【8月更文挑战第25天】Java 8 推出了革命性的 Lambda 表达式特性,这是一种匿名函数形式,支持任意数量参数及返回值,简化了代码编写。其基本语法为 `(parameters) -&gt; expression` 或 `(parameters) -&gt; { statements; }`。例如,遍历集合可从使用匿名内部类变为简洁的 `names.forEach(name -&gt; System.out.println(name))`。
42 0
|
13天前
|
设计模式 Java
结合HashMap与Java 8的Function和Optional消除ifelse判断
`shigen`是一位致力于记录成长、分享认知和留住感动的博客作者。本文通过具体代码示例探讨了如何优化业务代码中的if-else结构。首先展示了一个典型的if-else处理方法,并指出其弊端;然后引入了策略模式和工厂方法等优化方案,最终利用Java 8的Function和Optional特性简化代码。此外,还提到了其他几种消除if-else的方法,如switch-case、枚举行、SpringBoot的IOC等。一起跟随shigen的脚步,让每一天都有所不同!
28 10
结合HashMap与Java 8的Function和Optional消除ifelse判断
|
8天前
|
并行计算 Java 测试技术
探索Java中的函数式编程
在本文中,我们将深入探讨Java中的函数式编程。我们会先了解什么是函数式编程以及为什么它如此重要。然后,通过一些简单的代码示例,展示如何在Java中应用函数式编程概念。最后,讨论在实际项目中如何利用函数式编程来提高代码的可读性和效率。
|
9天前
|
Java API 开发者
探索Java中的函数式编程
本文深入探讨了Java中的函数式编程,这是一种强调使用不可变数据和避免共享状态的编程范式。我们将从基础概念、核心特性以及实际应用案例三个方面,全面解析函数式编程在Java中的魅力和价值。
|
8天前
|
Java C语言
5-13|Java的函数式编程
5-13|Java的函数式编程
|
2月前
|
并行计算 Java 大数据
Java函数式编程:一场编程范式的革命,让你的代码焕发新生!
【8月更文挑战第30天】Java函数式编程是一种基于数学函数理论的编程范式,强调数据处理的不可变性和纯函数使用,通过将函数视为第一类对象,实现更简洁、易读的代码结构,在数据流处理与并行计算中尤为突出。与命令式编程关注执行步骤不同,函数式编程侧重描述计算目标而非具体操作流程,减少了状态变化,使代码更清晰易维护。在Java中,函数式编程通过降低副作用和状态依赖简化了复杂度,并提高了代码质量和测试性,尤其是在Java 8的Stream API中得到了充分体现,能够自动优化多核处理器上的并行处理性能。
38 2
|
2月前
|
分布式计算 Java API
Java 8带来了流处理与函数式编程等新特性,极大提升了开发效率
Java 8带来了流处理与函数式编程等新特性,极大提升了开发效率。流处理采用声明式编程模型,通过filter、map等操作简化数据集处理,提高代码可读性。Lambda表达式支持轻量级函数定义,配合Predicate、Function等接口,使函数式编程无缝融入Java。此外,Optional类及新日期时间API等增强功能,让开发者能更优雅地处理潜在错误,编写出更健壮的应用程序。
26 1
|
2月前
|
Java API 开发者
Java的变革风暴:Lambda表达式与函数式编程如何颠覆你的代码世界
【8月更文挑战第7天】Java 8引入了Lambda表达式和函数式编程,显著增强了代码的简洁性和可读性。Lambda表达式作为匿名函数,简化了集合操作和事件处理等场景。其基本语法包括参数、箭头及表达式主体。结合Java 8的Stream API,开发者能以声明式风格处理数据,如筛选和转换集合,无需显式循环或条件判断,使得代码更清晰、高效。这些新特性丰富了Java的编程模型,提升了开发效率,并有助于代码的维护。
32 1
|
2月前
|
安全 Java API
Java 8 流库的魔法革命:Filter、Map、FlatMap 和 Optional 如何颠覆编程世界!
【8月更文挑战第29天】Java 8 的 Stream API 通过 Filter、Map、FlatMap 和 Optional 等操作,提供了高效、简洁的数据集合处理方式。Filter 用于筛选符合条件的元素;Map 对元素进行转换;FlatMap 将多个流扁平化合并;Optional 安全处理空值。这些操作结合使用,能够显著提升代码的可读性和简洁性,使数据处理更为高效和便捷。
36 0
下一篇
无影云桌面