Optional<T>:一个让代码中没有null的存在

1,449 阅读5分钟

概述

本文将介绍JDK 1.8后引入的一个Util ——Optional。

文章将从原理上、用法上以及给出实际样例的方式做说明。

介绍

在开发的过程中,Null Point Exception 永远都是程序员心中的痛,一不小心没有做 (object != null )的处理,系统就能给你无情的抛出一个Null point Exception。

哪怕连Null的发明人”东尼·霍尔“也称NULL :”I call it my billion-dollar mistake“。

那么为了隐藏Null(空指针)的不确定性,Optional出现了。

什么是Optional

A container object which may or may not contain a non-null value. If a value is present, isPresent() will return true and get() will return the value.
Additional methods that depend on the presence or absence of a contained value are provided, such as orElse() (return a default value if value not present) and ifPresent() (execute a block of code if the value is present).
This is a value-based class; use of identity-sensitive operations (including reference equality (==), identity hash code, or synchronization) on instances of Optional may have unpredictable results and should be avoided.
Since: 1.8

  1. 以上为Optional源码中关于Optional的介绍,他从以下几方面介绍了Optional。
  2. Optional是一个Container,即容器,可以用来存储可能是、也可能不是的non-null的value;简而言之就是一个可以为null的值。
  3. 其余提供的方法,均取决于容器中是否包含值,如使用isPresent()、orElse()均会根据是否包含值处理。
  4. 是一个value-based class (基于值的类),实际做的是引用操作,不要拿去做啥序列化、hash操作等,初始化后也不可变更容器内的值。否则后果自负!
  5. 从1.8开始提供。

细看源码

实际上我们在阅读源码后,可以看到以下几点:

  1. Optional是没有对外的构造器的,构造器均为private。

  2. 提供方法做初始化。

    1. Optional.empty()                           构建一个空的Optional
    2. Optional.ofNullable(T value)        传入一个值,可以为Null,构建一个Optional
    3. Optional.of(T value)                      传入一个值,不能为Null,构建一个Optional
  3. 提供一系列方法做值获取。

    1. get()     获取Optional中存储的值,若为Null将抛出”NoSuchElementException“
    2. orElse(T other)                                                  获取Optional中存储的值,若为Null将返回参数传入的对象
    3. orElseGet(Supplier<? extends T> other)        获取Optional中存储的值,若为Null将返回传入的函数执行的结构
    4. orElseThrow(Supplier<? extends X> exceptionSupplier)    获取Optional中存储的值,若为Null将抛出传入的Exception函数执行的结果
  4. 提供检查接口

    1. boolean isPresent()                                                                     检测存储的值是否为Null,若为null则为false,否则为true
    2. public void ifPresent(Consumer<? super T> consumer)         检测存储的值是否为Null,若为不为null则执行传入的函数式操作
  5. 提供一系列的流式操作接口

    1. Optional flatMap(Function<? super T, Optional> mapper)          与#b中的map一样的逻辑,但是返回的是一个Optional 包裹(wrapper)起来的数据;即按照Map的示例,他返回的是Optional name
    2. Optional map(Function<? super T, ? extends U> mapper)    传入一个U对应的函数,返回该数据,如Optional中,Optional.map(User::getName),即可返回对应的Name
    3. Optional filter(Predicate<? super T> predicate)        按照函数式操作筛选Optional值,里面可以传入  .filter(state -> state.runningThread.equals(thread))这类型的操作

以上就是Optional中所有的方法,有兴趣的可以去看看源码,封装性做的非常好,很值得学习。每一个方法上均有对应的说明,注释之详细,值得咱们学习。

实际操作

基本样例

让我们以一个工作中经常使用到的样例开始。

样例1:从数据库查询出一组PO数据,将其筛选数据后,转换为DTO传输给到前端。

代码可以是这样的:

if(CollectionUtils.isEmpty(userPOList)){
   return new ArrayList<>();
}
// 简略编写了convertorList<UserDTO> userDTOList = UserConvertor.userPo2Dto(userPOList);
return userDTOList;

可以看到,我对返回的Collection是否为空与null使用了CollectionUtils.isEmpty()方法做判定,若为”empty“,我提前返回了一个空的List,由上层调用方处理。

如果使用Optional处理,会是这样的情况。

List<UserPO> userPOList = UserDAO.selectList(userQuery);

List<UserDTO> userDTOList = new ArrayList<>();
 Optional.ofNullable(userPOList)
     .orElse(new ArrayList<>())
     .forEach(a -> userDTOList.add(userPo2Dto(a)));

以上代码中,UserDAO.selectList返回的依旧是一个List,我在将数据放入后,使用了orElse方法做获取操作,获取到后直接使用了Stream的forEach操作。

后续将DAO层直接返回Optional,代码第一行便直接省略了。

线上实际代码

private Set<Integer> getRoleIdsByUserId(int tenantId, int userId) {
  return userRoleQuery.getInfoByUserId(tenantId, userId)
      .orElse(new ArrayList<>())
      .stream()
      .map(UserRoleRelationDO::getRoleId)
      .collect(Collectors.toSet());
}

以上代码为实际开发过程中写的,当然,这段代码并未使用到Optional的Map、Filter功能,仅仅是用了其orElse以及Stream操作。但是可以做一个样例。

Optional<CustomerPO> customerPoOptional = customerQueryService.getCustomerById(getTenantId(), customerId);
CustomerPO customerPO = customerPoOptional.orElseThrow(() -> ExceptionFactory.bizException("未查询到该用户信息!"));

上述为展示orElseThrow()使用方式,若值为null,则抛出对应的Exception。

进阶说明

在上述的一些简单使用中,我们会发现,两个检查接口 isPresent(),ifPresent()都没有被使用到,还有都是使用的orElseGet()、orElse(),并不直接使用get()。这是为什么呢?

一个是,get()方法若存储的数据为null,则将直接抛出错误,所以该方法,都是在一定确认存在数据的时候进行调用。

所以存在一个准则:

避免使用Optional.get()。如果你不能证明存在可选项,那么永远不要调用get() 另外,isPresent()、ifPresent()一般的用法为在做了一系列的判定后,检查是否还存在数据时使用,平时的使用均是获取数据流式操作就解决了。

以下给出一个示例(示例来自GitHub):

@Override
  public boolean isExecutedBy(Thread thread) {
    return Optional.ofNullable(runningState.get())
      .filter(state -> state.runningThread.equals(thread))
      .isPresent();
  }

使用建议

最后再给出几个使用准则:

不要在字段,方法参数,集合中使用Optional。 即,不要将入参,或则某个字段定义为Optional,让外界传输进来,Optional是用在结果不确定时, 而当入参中使用时,即代表调用方也不知道该数据是否为null,这是很荒唐的。

使用过程中,更多的是,将一个返回定义为Optional,给到调用方直接使用即可。