《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 yes的练级攻略 「是Yes呀」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注**微信公众号:【芋道源码】**有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言将得到认真回复。甚至不知道如何读源码也可以请教噢
  4. 新的源码解析文章实时收到通知。每周更新一篇左右
  5. 认真的源码交流微信群。

前言

又是一个阳光明媚的下午,群里面又在讨论技术,啧啧。

马哥发言道:

图片

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

Optional<User> userOption = Optional.ofNullable(userService.getUser(...));
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。

图片

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

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

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

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

Yes yes = getYes();
if (yes != null) {
Address yesAddress = yes.getAddress();
if (yesAddress != null) {
Province province = yesAddress.getProvince();
System.out.println(province.getName());
}
}
throw new NoSuchElementException(); //如果没找到就抛错

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

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

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

说到这,我想提个问:

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

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

Optional 源码

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

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

图片

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

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

图片

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

图片

先判断 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 的源码,现在来回答下上面的问题,看注释:

图片

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

图片

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

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

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

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

看下这个代码:

图片

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

我们来看下源码。

图片

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

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

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

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

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

图片

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

图片

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

图片

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

图片

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

图片

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

图片

在 stackoverflow 有个类似的提问。

图片

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,但没有在那个版本中实现。

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

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

图片

文章目录
  1. 1. 前言
  2. 2. 那 Optional 应该如何用呢?
  3. 3. Optional 源码
    1. 3.0.1. 关于 Optional 还有个性能问题,我们看一下:
  • 4. 总结