原文: https://mp.weixin.qq.com/s?__biz=MzkxNTE3NjQ3MA%3D%3D&mid=2247490309&idx=1&sn=a2fea9ad6203590199e6a12ebaf21924&scene=45#wechat_redirect

马哥发言道:

Java8的Optional是不是鸡肋? - 图1

原因是他的一位同事请假了,他接手他的代码 8 天了,要受不了,来看下他同事 Optional 的使用:

  1. Optional<User> userOption = Optional.ofNullable(userService.getUser(...));
  2. if (!userOption.isPresent()) {....}

if 里面还有 Optional 套 Optional ,连环判断 isPresent 的。

关于 Optional 老早之前我就看到很多争论,有好多怒喷 Optional 鸡肋,是个糟糕的设计,巴拉巴拉。

先抛开这些不管,反正如果平日是按照以上的用法来用 Optional 的,还是直接用 if(user != null){....} 判空算了,何必包一层 Optional,再判断呢?这样使用 Optional 是不对滴,画蛇添足。

那 Optional 应该如何用呢?

Optional 的真实执行逻辑是否与你所想的一样?

今天同样还是深入源码看看。

我们先来看看 Optional 设计出来的意图是什么, Java 语言架构师 Brian Goetz 是这么说的:

Our intention was to provide a limited mechanism for library method return types where there needed to be a clear way to represent “no result”, and using null for such was overwhelmingly likely to cause errors.

意思就是:Optional 可以给返回结果提供了一个表示无结果的值,而不是返回 null。

简单理解下,Optional 其实就是一个壳,里面放着原先的值,至于这个值是不是 null 另说,反正拿到的这个壳肯定不是 null。

Java8的Optional是不是鸡肋? - 图2

网上比较流行的说法是 Optional 可以避免空指针,我不太赞同这种说法。因为最终的目的是拿到 Optional 里面存储的值,如果这个值是 null ,不做额外的判断,直接使用还是会有空指针的问题。

我认为 Optional 的好处在于可以简化平日里一系列判断 null 的操作,使得用起来的时候看着不需要判断 null,纵享丝滑,表现出来好像用 Optional 就不需要关心空指针的情况。

而事实上是 Optional 在替我们负重前行,该有的判断它替我们完成了,而且用了 Optional 最后拿结果的时候还是小心的,盲目 get 一样会抛错,Brian Goetz 说 get 应该叫 getOrElseThrowNoSuchElementException。

我们来看一下代码就很清楚 Optional 的好处在哪儿了。比如现在有个 yesSerivce 能 get 一个 Yes,此时需要输出 Yes 所在的省,此时的代码是这样的:

  1. Yes yes = getYes();
  2. if (yes != null) {
  3. Address yesAddress = yes.getAddress();
  4. if (yesAddress != null) {
  5. Province province = yesAddress.getProvince();
  6. System.out.println(province.getName());
  7. }
  8. }
  9. throw new NoSuchElementException(); //如果没找到就抛错

如果用 Optional 的话,那就变成下面这样:

  1. Optional.ofNullable(getYes())
  2. .map(a -> a.getAddress())
  3. .map(p -> p.getProvince())
  4. .map(n -> n.getName())
  5. .orElseThrow(NoSuchElementException::new);

可以看到,如果用了 Optional,代码里不需要判空的操作,即使 address 、province 为空的话,也不会产生空指针错误,这就是 Optional 带来的好处!

说到这,我想提个问:

如果在 a.getAddress() 时拿不到值的话,你说是会继续执行map(p -> p.getProvince()) 还是直接跳到 orElseThrow? 或者反过来如果 map(n -> n.getName()) 不为空,你说 orElseThrow 这个方法会不会执行?

接下来我们就来看下源码,看看 Optional 的实现机制。

Optional 源码

Optional 的代码十分简短且简单,如果去掉注释,我估计就 100 来行。

来看下几个关键的成员变量:

Java8的Optional是不是鸡肋? - 图3

符合前面提到的:Optional 就是个壳,里面的 value 才是正主。并且内置了一个 EMPTY 对象,用来替换当 value 为 null 时候的壳。

现在看下上面演示的 map 方法,看看它的内部实现是如何让我们不需要做非空判断的。

Java8的Optional是不是鸡肋? - 图4

可以看到很简单,没几行代码,我把方法中的两个调用实现都贴上去,这样对着看应该会更清晰:

Java8的Optional是不是鸡肋? - 图5

先判断 value 是否为空,如果是空的话说明真正要是值是空的,此时直接返回一个 empty(),还记得上面的 empty 方法吧?直接方式事先创建的空 Optional 。

如果 value 不为空,那说明值是存在的,因此调用 mapper (就是上面我们写的 a.getAddress 之类的) 来操作一波这个 value,并且用 Optional.ofNullable 包了一层,这个方法内部也看到了,如果 value 是空的话,也是返回空 Optional,否则就利用 of 包裹 value 成 Optional 返回。

因此,不论你 Optional 里面到底有没有值,我 map 都能处理!如果你是空,我就返回空 Optional ,如果你有值,ok 我包裹成 Optional 返回,反正不论怎样,调用 map 的返回值都会是一个 Optional,而不是 null,所以执行时不会产生空指针的情况。

还记得上面的提问吗?结合 map 的源码,现在来回答下上面的问题,看注释:

Java8的Optional是不是鸡肋? - 图6

截个 orElseThrow 的实现,就是判断下 value ,如果是 null 就抛错。

Java8的Optional是不是鸡肋? - 图7

结合源码我们知道了答案:即使 Optional.ofNullable 返回的是空 Optional ,下面的 map 逻辑还是会执行,不会因为中间得到空值而直接跳到orElseThrow执行,这和我们平日知晓的 if else 逻辑不太一样,不为空orElseThrow也一样会执行,就是判断 value!= null然后直接返回 value 的值了。

好了,逻辑就是这么简单!上面之所以说是 Optional 在替我们负重前行,是因为该有的判断一个都没少,只是它替我们做了而已。

关于 Optional 还有个性能问题,我们看一下:

Optional 里有 orElseGet 和 orElse 这两个看起来挺相似的方法,都是处理当值为 null 时的兜底逻辑。可能你也在一些文章上看到说用 orElseGet 不要用 orElse ,因为在 Optional 有值时候 orElse 仍然会调用方法,所以后者性能比较差。其实从上面分析我们知道不论 Optional 是否有值,orElse 和 orElseGet 都会被执行,所以是怎么回事呢?

看下这个代码:

Java8的Optional是不是鸡肋? - 图8

这样看来 orElse 确实性能会差,奇怪了,难道是 bug?

我们来看下源码。

Java8的Optional是不是鸡肋? - 图9

可以看到两者的入参不同,一个就是普通参数,一个是 Supplier。我们已经得知不论Optional.ofNullable 返回的是否是空 Optional,下面的逻辑还是会执行,所以 orElse 和 orElseGet 这两个方法无论如何都会执行。

因此 orElse(createYes()) 会被执行,在参数入栈之前,执行了 createYes 方法得到结果,然后入栈,而 orElseGet 的参数是 Supplier,所以直接入栈,然后在调用 other.get 的时候,createYes 方法才会被触发执行,这就是两者的区别之处。

所以才会造成上面表现出的性能问题,因此不是 BUG,也不是有些文章说的 Optional 有值 orElse 也会被执行而 orElseGet 不会执行这样不准确的说法,相信现在你的心里很有数了。

既然都讲到这了,把 Optional 剩下几个方法讲讲完吧,没几个了。

来看个 of 和 ofNullable 的对比,看下注释应该很清晰了。

Java8的Optional是不是鸡肋? - 图10

再来看个 isPresent() 和 ifPresent(Consumer<? super T> consumer),两者名字有细微的差别,is 和 if。

Java8的Optional是不是鸡肋? - 图11

还有个 get,这个方法要小心,如果没做好判断,直接调用,当是空 Optional 时会抛错的。

Java8的Optional是不是鸡肋? - 图12

还有个 filter 逻辑 和 map 的差不多,用于过滤数据,平日基本是就是先 filter 再 map,属于基操。

Java8的Optional是不是鸡肋? - 图13

还有个 flatMap ,这个和 map 逻辑一模一样,就入参有点不一样,用在返回值不是普通对象,是 Optional 包裹的对象的场景。

Java8的Optional是不是鸡肋? - 图14

这里又得提一点了,关于 POJO 里面的属性是否应该被 Optional 包裹,或者说是否应该把 get 方法包裹成 Optional 返回,类似下面这样的代码。

Java8的Optional是不是鸡肋? - 图15

在 stackoverflow 有个类似的提问。

Java8的Optional是不是鸡肋? - 图16

Brian Goetz 给了回答,我直接翻译了:你可能永远不应该将它用于返回结果数组或结果列表的内容,而应该返回空数组或空列表。你几乎不应该将它用作某个字段或方法参数,我认为经常使用它作为 getters 的返回值肯定是过度使用。

下面也有一堆不服的,说这发言更像是您自己所认为的,而没有什么依据表明这样用有什么不好,反正我不敢发言,神仙打架瑟瑟发抖。

不过我个人倾向于 Brian Goetz,我觉得 Optional 的用处就是逻辑处理的时候避免判空,仅此而已,所以 POJO 本该如何还是如何,Optional 应该交由逻辑处理代码来用。

好了,把 Optional 的方法都讲完了,可以看到还是很简单的,也没有什么骚操作,比看并发包的简单多了。

总结下来 Optional 主要是简化一系列判空操作,执行过程是一条龙走到底的,你有几个 filter 和 map 不论得到的值空不空,都是执行到底包括 orElse 的逻辑。

再提一个题外话,在 oracle 官网上我看到一篇关于 Optional 的文章,上面写道:

像 Groovy 是利用 ?. 来避免判空的,例如这个代码:

String version = computer?.getSoundcard()?.getUSB()?.getVersion();

后面写了个 note: 请注意,它很快也将被包含在 c# 中,它曾被提议用于 Java SE 7,但没有在那个版本中实现。

咱也不知道为啥没被接受,反正我觉得上面这写法挺清爽的。

再分享下网上看到的一副图:

Java8的Optional是不是鸡肋? - 图17