我们经常在编程的遇到需要做空判断的场景。
例如拉哥最近遇到的一个场景,需要获取任务节点的执行完成时间,是Date类型的,但是上游需要时间的毫秒,所以写了这么一段代码
public Result<TaskInfoVo> getTaskInfo(String taskId){
TaskNode taskNode = taskExecutor.getByTaskId(String taskId);
//返回任务视图
TaskInfoVo taskInfoVo = TaskInfoVo
.builder()
.taskName(taskNode.getName())
.finishTime(taskNode.getFinishTime().getTime())
.user(taskNode.getUser())
.memo(taskNode.getMemo())
.build()));;
return Result.ok(taskInfoVo);
}
class TaskInfoVo {
/**
** 完成时间
**/
Long finishTime;
}
但是运行时发现任务的执行时间可能为null,taskNode.getFinishTime().getTime() 这里会产生NPE(空指针异常)。
如果不使用 Optional,一般判空的方式可以这么写:
//第一种判空
if (Objects.notNull(taskNode.getFinishTime())) {
taskInfoVo.set(taskNode.getFinishTime().getTime());
}
//第二种判空 保留builder模式
TaskInfoVo
.builder()
.finishTime(taskNode.getFinishTime() == null ? null : taskNode.getFinishTime().getTime())
.build()));
第一种方式就不能使用builder模式,值的设置割裂开了。
第二种方式采用三目表达式也还算清晰,只是执行了二次 getFinishTime(),如果在不使用Optional的二种方式,更推荐第二种,清晰一致。
再说第三种,使用Optional 方式。如下所示:
public Result<TaskInfoVo> getTaskInfo(String taskId){
TaskNode taskNode = taskExecutor.getByTaskId(String taskId);
//返回任务视图
TaskInfoVo taskInfoVo = TaskInfoVo
.builder()
.taskName(taskNode.getName())
.finishTime(Optional.ofNullable(taskNode.getFinishTime()).map(date -> date.getTime()).orElse(null))
.user(taskNode.getUser())
.memo(taskNode.getMemo())
.build()));;
return Result.ok(taskInfoVo);
}
首先,我们使用 Optional.ofNullable
把可能为空的值包了一层,然后用map和orElse 来设置存在值和为空分别的结果。
我们来看看Optional 内部的实现细节,代码很清晰,也很简单。
/**
**@since 1.8 jdk1.8引入
**/
public final class Optional<T> { // final 修饰,不能被继承,也就是不允许重写
/**
* Common instance for {@code empty()}. 空对象,但注意,不是null
*/
private static final Optional<?> EMPTY = new Optional<>();
/**
* 存储的值
*/
private final T value;
private Optional() {
this.value = null;
}
//空数据
public static<T> Optional<T> empty() {
@SuppressWarnings("unchecked")
Optional<T> t = (Optional<T>) EMPTY;
return t;
}
//带参构造函数
private Optional(T value) {
//value 不能为空
this.value = Objects.requireNonNull(value);
}
//一般不建议使用of,因为传入的值不允许为空,否则抛异常
public static <T> Optional<T> of(T value) {
return new Optional<>(value);
}
//一般确定value不为null,才调用get
public T get() {
if (value == null) {
throw new NoSuchElementException("No value present");
}
return value;
}
//是否存在 present是个好词,源码经常用
public boolean isPresent() {
return value != null;
}
//如果存在,调用consumer 消费value值
public void ifPresent(Consumer<? super T> consumer) {
if (value != null)
consumer.accept(value);
}
//判断,predicate有test函数,filter返回过滤后Optional
public Optional<T> filter(Predicate<? super T> predicate) {
Objects.requireNonNull(predicate);
if (!isPresent())
return this;
else
return predicate.test(value) ? this : empty();
}
//映射转化 mapper
public<U> Optional<U> map(Function<? super T, ? extends U> mapper) {
Objects.requireNonNull(mapper);
if (!isPresent())
return empty();
else {
return Optional.ofNullable(mapper.apply(value));
}
}
//和map相比,flatMap返回结果不能为空,否则抛NPE
public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper) {
Objects.requireNonNull(mapper);
if (!isPresent())
return empty();
else {
return Objects.requireNonNull(mapper.apply(value));
}
}
//值不为空 返回 否则返回其他
public T orElse(T other) {
return value != null ? value : other;
}
//值不为空,返回,否则返回传入的其他值
public T orElseGet(Supplier<? extends T> other) {
return value != null ? value : other.get();
}
//值不为空,直接返回,否则丢出提供的异常
public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
if (value != null) {
return value;
} else {
throw exceptionSupplier.get();
}
}
}
//列出了 Objects
class Objects {
public static <T> T requireNonNull(T obj) {
if (obj == null)
throw new NullPointerException();
return obj;
}
}
我们来分别看一下这些方法的使用场景
希望为空时获得默认值
Task defalutTask = new Task("defalutTask", defalutTaskInfo); return Optional.ofNullable(task).orElse(defalutTask);
希望为空时进行函数求值
Task defalutTask = new Task("defalutTask", defalutTaskInfo); return Optional.ofNullable(task).orElseGet(() -> assembleDefaultTask()); private Task assembleDefaultTask() { Task currentTask = ExecutorManager.getCurrentTaskFromContext(); currentTask.reset(); return currentTask; }
orElseGet 与 orElse 的区别在于 orElseGet 的参数是个 Supplier 对象,orElse 是值。
Supplier 是java8 引入的。Supplier 接口仅包含一个无参的方法: T get() 。用来获取一个泛型参数指定类型的对象数据。
.orElseGet(() -> assembleDefaultTask());
空判断
if (taskOptional.isPresent()) { //doSomeThing(); }
如果存在,执行下一步消费者动作。
taskOptional.ifPresent([Consumer]<? super [T]> consumer) //还是以日期处理为例 Optional.ofNullable(previewStep.getFinishTime()).ifPresent(date -> processPreviewVO.setFinishTime(date.getTime()));
消费者大家不陌生,就是执行一套消费动作,lamada 的写法简化了代码,但是降低了可读性,
date -> processPreviewVO.setFinishTime(date.getTime())
实际上就是这段代码:new Consumer<Date>() { @Override public void accept(Date date) { processPreviewVO.setFinishDate(date.getTime()); } }
orElse() 和 orElseGet() 的不同之处
User user = null; logger.debug("Using orElse"); User result = Optional.ofNullable(user).orElse(createNewUser()); logger.debug("Using orElseGet"); User result2 = Optional.ofNullable(user).orElseGet(() -> createNewUser()); private User createNewUser() { logger.debug("Creating New User"); return new User("extra@gmail.com", "1234"); }
输出:
Using orElse Creating New User Using orElseGet Creating New User
如果user 为null 时结果是一致的;
但是user 不为null 时,行为是不同的,我们来看下
User user = new User("john@gmail.com", "1234"); logger.info("Using orElse"); User result = Optional.ofNullable(user).orElse(createNewUser()); logger.info("Using orElseGet"); User result2 = Optional.ofNullable(user).orElseGet(() -> createNewUser());
输出:
Using orElse Creating New User Using orElseGet
为什么呢?
因为orElse ( T value) 函数入参是普通变量,因为会将函数计算好的结果作为参数传进去。
但是orElseGet(Supplier<? extends T> other)入参实际上是个 Supplier 对象, 因为只有一个方法,所以可以用lambda写法。
public interface Supplier<T> { /** * Gets a result. * * @return a result */ T get(); }
对象的函数没有被调用是不会自己主动执行的。
值转化
我们先来看map的例子,实际上面已经演示过了,下面再讲解一下。
User user = new User("angela@gmail.com", "niubi"); String email = Optional.ofNullable(user) .map(u -> u.getEmail()).orElse("default@gmail.com");
map可以对结果进行转化,返回的是依然是Optional,方便你后续的链式调用。
值过滤
User user = new User("angela@gmail.com", "666"); Optional<User> result = Optional.ofNullable(user) .filter(u -> u.getEmail() != null && u.getEmail().contains("@"));
这个filter 方法实际上类似断言,我总觉得对集合元素进行遍历,判断是否符合预期才更适合叫做 filter ,这个功能用的比较少。
Optional类的链式方法
比如现在我们有这个一个场景,智能游戏机器人需要让安琪拉释放火球技能,它需要判断自己英雄池是否有安琪拉,并且安琪拉火球技能是否ready。
代码实现:
String result = Optional.ofNullable(heroPool) .flatMap(heroPool -> heroPool.getHero("angela")) .flatMap((Angela)hero -> hero.getFireBall()) .map(c -> c.fire()) .orElse("failure");
如果用常规代码,需要做很多层判空处理。
注意事项
不要将null赋给Optional 虽然Optional支持null值,但是不要显示的把null 传递给Optional
尽量避免使用Optional.get()
当结果不确定是否为null时,且需要对结果做下一步处理,使用Optional;
在类、集合中尽量不要使用Optional 作为基本元素;
尽量不要在方法参数中传递Optional;
大胆使用map、filter 重构代码
//1. map 示例 if ( hero != null){ return "hero : " + hero.getName() + " is fire..."; } else { return "angela"; } //重构成 String heroName = hero .map(this::printHeroName) .orElseGet(this::getDefaultName); public void printHeroName(Hero dog){ return "hero : " + hero.getName() + " is fire..."; } public void getDefaultName(){ return "angela"; } //2. filter示例 Hero hero = fetchHero(); if(hero != null && hero.hasBlueBuff()){ hero.fire(); } //重构成 Optional<Hero> optionalHero = fetchHero(); optionalHero .filter(Hero::hasBlueBuff) .ifPresent(this::fire);
不要使用 Optional 作为Java Bean Setter方法的参数
因为Optional 是不可序列化的,而且降低了可读性。
不要使用Optional作为Java Bean实例域的类型
原因同上。
如果看完觉得有收获,记得评论点赞转发三连。