Java 8 Optional 能否解决 NPE ?

答案

  Java 8 的 Optional 并不能解决 NPE,但是可以预防 NPE 的发生!

空指针问题

  作为 Java 开发工程师,如果说 NullPointerException 是最常见的异常且没有之一,肯定是没有人反驳的.相对于其他异常, NPE 可谓是司空见惯了.
  NPE 的出现可以简单归纳为两种情况

1. 数据问题
2. 代码问题

  作为研发人员,这两种情况是如何导致NPE,就不在此赘述.我们来仔细论述一下这两种情况下导致的NPE 该如何处理!

数据问题

  首先是数据问题,这种情况的下发生的 NPE,我们应该首先处理数据,然后排查脏数据产生的原因,而不是在代码中写下Objects.isNull(xxx).
  这种情况下的 Optional 完全没有发挥余地,Optional 是不可能无中生有的.也解决不了业务流程中引入脏数据的问题,并且如果强行使用 Optional 令代码暂时不报错,不抛异常,非常可能导致使用数据的后序流程产生更大的错误和代价!

代码问题

  作为代码问题导致的NPE,我们是否可以使用 Optional 解决呢?就如开篇答案所写是不可能的!
在网络上绝大多数的文章中都在写 Optional 是如何优雅的解决NPE,并列上了一些看似正确的代码.比如这样的代码


网络文章的解决方案

  但是我们仔细阅读代码,并分析解决方案.我们可以看到这些所谓的解决方案无非是使用了 Optional 实现了更优雅的写法,是真的解决了 NPE 么?并没有.
  现实中的业务代码中 NPE 的出现,绝大多数情况是我们业务经验缺失或思考不够严谨,忽略了边界条件和忘记判空所引发的.这时候使用 Optional 也只是一种更优雅的补偿方式,而并不是一种所谓的彻底解决方案!

Optional 存在的意义

  我们在上文中对现有大多数人的想法和代码做了一番批判,那是不是 Optional 的存在就是无意义的呢?
  那当然不是, Optional 真正的作用是警示并预防NPE 的出现,并将隐形的业务知识显性化!我们慢慢分析这句话.

警示并预防NPE

  官方的文档中建议表示不要将 Optional 作为方法入参和对象属性,并建议返回值使用Optional.
  我们看以下代码来理解官方的这个建议

    /**
     * 根据输入数据获取一个随机数
     *
     * @param upperLimit 上限
     * @param lowerLimit 下限
     * @return ranomNumber
     */
    public Optional<Integer> getRandomNumber(Optional<Integer> upperLimit, Optional<Integer> lowerLimit) {

        int max = Objects.isNull(upperLimit) ? Integer.MAX_VALUE : upperLimit.orElse(Integer.MAX_VALUE);
        int min = Objects.isNull(lowerLimit) ? Integer.MIN_VALUE : lowerLimit.orElse(Integer.MIN_VALUE);

        Random r = new Random();
        OptionalInt first = r.ints(min, max).findFirst();

        if (first.isPresent()) {
            return Optional.of(first.getAsInt());
        }
        return Optional.empty();
    }

    /**
     * 不使用 Optional
     */
    public Integer getRandomNumber(Integer upperLimit, Integer lowerLimit) {

        int max = upperLimit != null ? upperLimit : Integer.MAX_VALUE;
        int min = lowerLimit != null ? lowerLimit : Integer.MIN_VALUE;

        Random r = new Random();
        OptionalInt first = r.ints(min, max).findFirst();

        if (first.isPresent()) {
            return first.getAsInt();
        }
        return null;
    }

  观察上面这个简单的案例,我们并没有因为使用了 Optional 就少做了判空操作,反而因为 Optional的使用,需要多一次对Optional 对象的判空
  只要我们逻辑严谨完全可以不用Optional,就写出健壮的代码,反而因为 Optional 做为入参的传入,我们需要额外的代码对 Optional 判空处理.

public static void main(String[] args) {

        // 响应类型暗示了出参为空的可能
        Optional<Integer> randomNumber = getRandomNumber(Optional.empty(), Optional.of(1));
        Integer integer = randomNumber.orElse(0);
        System.err.println("获取到的随机数" + integer.toString());

        // 不阅读源码,对出参为空的情况并不知情
        Integer random = getRandomNumber(null, 1);
        // 可能出现NPE
        System.err.println("获取到的随机数" + random.toString());
    }

  但是方法返回值使用 Optional<Integer> 却实实在在的提醒了调用 getRandomNumber 方法的研发人员,获取到的随机数可能是空的,要随时思考空值对系统产生恶劣影响和破坏的可能.
  至此,我们已经可以体会官方推荐的含义了.将 Optional 做为方法返回值是有实际好处,并有意思的,能够警示我们,并督促我们提前处理可能为空的情况!

业务知识显性化

  作为研发人员,尤其刚入职新公司或刚刚接触新业务的研发,写出 NPE 代码的可能性是最高的,这是因为他们的业务知识的积累不够和缺失,对一些常用业务方法出现空值的情况并不知情,也并未深入源码阅读的结果.
  使用 Optional 可以提醒他们,并明确业务返回值为空的可能性,并谨慎处理返回值.将部分隐藏在大脑和源码中的隐形业务知识,通过代码实现了显性的表达!降低了出现低级错误的可能,和不知情带来的过错.

End

  Java 8 Optional 能否解决 NPE ? 不能!
  Java 8 Optional 有实践和使用的意义么 ? 非常有!
  至此,标题中的问题,我们已经论述清楚,如有不同意见或想法,可以下方评论,共同进步!