Java Optional使用的最佳实践

我们经常在编程的遇到需要做空判断的场景。

例如拉哥最近遇到的一个场景,需要获取任务节点的执行完成时间,是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;
    }
  }

我们来分别看一下这些方法的使用场景

  1. 希望为空时获得默认值

    Task defalutTask = new Task("defalutTask", defalutTaskInfo);
    return Optional.ofNullable(task).orElse(defalutTask);
  2. 希望为空时进行函数求值

    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());

  3. 空判断

    if (taskOptional.isPresent()) {
        //doSomeThing();
    }
  4. 如果存在,执行下一步消费者动作。

    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());
      }
    }
  5. 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();
    }

    对象的函数没有被调用是不会自己主动执行的。

  6. 值转化

    我们先来看map的例子,实际上面已经演示过了,下面再讲解一下。

    User user = new User("angela@gmail.com", "niubi");
    String email = Optional.ofNullable(user)
      .map(u -> u.getEmail()).orElse("default@gmail.com");

    map可以对结果进行转化,返回的是依然是Optional,方便你后续的链式调用。

  7. 值过滤

    User user = new User("angela@gmail.com", "666");
    Optional<User> result = Optional.ofNullable(user)
     .filter(u -> u.getEmail() != null && u.getEmail().contains("@"));

    这个filter 方法实际上类似断言,我总觉得对集合元素进行遍历,判断是否符合预期才更适合叫做 filter ,这个功能用的比较少。

  8. Optional类的链式方法

    比如现在我们有这个一个场景,智能游戏机器人需要让安琪拉释放火球技能,它需要判断自己英雄池是否有安琪拉,并且安琪拉火球技能是否ready。

    4a334cf1d3fb946639e55f02794beb9e.png

    代码实现:

    String result = Optional.ofNullable(heroPool)
          .flatMap(heroPool -> heroPool.getHero("angela"))
          .flatMap((Angela)hero -> hero.getFireBall())
          .map(c -> c.fire())
          .orElse("failure");

    如果用常规代码,需要做很多层判空处理。

  9. 注意事项

  • 不要将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实例域的类型

    原因同上。

如果看完觉得有收获,记得评论点赞转发三连。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值