概述
本文将介绍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
- 以上为Optional源码中关于Optional的介绍,他从以下几方面介绍了Optional。
- Optional是一个Container,即容器,可以用来存储可能是、也可能不是的non-null的value;简而言之就是一个可以为null的值。
- 其余提供的方法,均取决于容器中是否包含值,如使用isPresent()、orElse()均会根据是否包含值处理。
- 是一个value-based class (基于值的类),实际做的是引用操作,不要拿去做啥序列化、hash操作等,初始化后也不可变更容器内的值。否则后果自负!
- 从1.8开始提供。
细看源码
实际上我们在阅读源码后,可以看到以下几点:
-
Optional是没有对外的构造器的,构造器均为private。
-
提供方法做初始化。
- Optional.empty() 构建一个空的Optional
- Optional.ofNullable(T value) 传入一个值,可以为Null,构建一个Optional
- Optional.of(T value) 传入一个值,不能为Null,构建一个Optional
-
提供一系列方法做值获取。
- get() 获取Optional中存储的值,若为Null将抛出”NoSuchElementException“
- orElse(T other) 获取Optional中存储的值,若为Null将返回参数传入的对象
- orElseGet(Supplier<? extends T> other) 获取Optional中存储的值,若为Null将返回传入的函数执行的结构
- orElseThrow(Supplier<? extends X> exceptionSupplier) 获取Optional中存储的值,若为Null将抛出传入的Exception函数执行的结果
-
提供检查接口
- boolean isPresent() 检测存储的值是否为Null,若为null则为false,否则为true
- public void ifPresent(Consumer<? super T> consumer) 检测存储的值是否为Null,若为不为null则执行传入的函数式操作
-
提供一系列的流式操作接口
- Optional flatMap(Function<? super T, Optional> mapper) 与#b中的map一样的逻辑,但是返回的是一个Optional 包裹(wrapper)起来的数据;即按照Map的示例,他返回的是Optional name
- Optional map(Function<? super T, ? extends U> mapper) 传入一个U对应的函数,返回该数据,如Optional中,Optional.map(User::getName),即可返回对应的Name
- 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,给到调用方直接使用即可。