Java8的Optional是不是鸡肋?

以下文章来源于yes的练级攻略 ,作者是Yes呀

yes的练级攻略

用接地气的话来分享一些后端技术或写一些想写的。

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

马哥发言道:

原因是他的一位同事请假了,他接手他的代码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,但没有在那个版本中实现。

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

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

(0)

相关推荐

  • 求求你,不要再使用!=null判空了!

    对于Java程序员来说,null是令人头痛的东西.时常会受到空指针异常(NPE)的骚扰.连Java的发明者都承认这是他的一项巨大失误. 那么,有什么办法可以避免在代码中写大量的判空语句呢? 有人说可以 ...

  • java8特性 Optional 工具类

    java8特性 Optional 工具类

  • 历史上的今天:编程语言中null引用的十亿美元错误

    这是Jerry 2021年的第 10 篇文章,也是汪子熙公众号总共第 281 篇原创文章. 今天是2021年1月17日,星期日,腊月初五. Jerry之前收到CSDN社区赠送的新年礼物,一本台历:&l ...

  • LeetCode之Missing Number

    LeetCode之Missing Number

  • java重构之返回Optional

    在Java 8之前,要编写一个在特定环境下无法返回任何值的方法时,有两种方法:要么抛出异常,要么返回null(假设这里返回类型是一个对象引用类型).但这两种方法都不够完美.通常情况下异常应该根据异常条 ...

  • 让绩效沦为“鸡肋”的5大主因,切不可犯!建议收藏

    导语: 为何很多企业想做的绩效管理变成了无效管理?我看到有好多企业都想推行绩效管理,我也看到很多企业在绩效管理的运行当中,出现了一些问题,比如说很多企业做了,但是做到一定程度发现流于形式,最后半途而废 ...

  • 一个鸡肋细节让你家乱10倍,90%中国家庭都做错了!

    来源|住范儿 ID | zhu-faner 装修中有一件小事,一不留神就会做错,那就是插座布局. 插座没做对 ,遍地插线板: 插座位置错了,怎么用怎么难受. 这样的尴尬情景,第一次装修的朋友们,几乎无 ...

  • 中控屏幕是鸡肋?没啥用?没那么简单!

    随着智能化的兴起,让大屏幕从手机.电视等转变到汽车行业,而且一个比一个大.车企费尽了心思,无论是合资还是国产,追求中控大彩屏好像成为了每个车企的目标和潮流.那么问题来了,中控屏真的是越大越好吗? 现在 ...

  • 津门故里,鸡肋的5A景区

    什么是天津味,提到天津你会想到什么?总觉得天津是直辖市中最低调的城市,离北京实在太近,在北京的光环下黯然失色.很少会有游客把天津作为主要出游目的地,基本上都是去北京顺带去天津玩一玩,或者是天津附近的游 ...

  • 卧室告别鸡肋的飘窗,这样做更实用

    卧室告别鸡肋的飘窗,这样做更实用! (点击下方视频号观看) 卧室告别鸡肋的飘窗,这样做更实用 现在的户型设计中,飘窗是比较常见的.但作为舶来品的飘窗,看似浪漫,实际上实用性是比较差的,大多数时候只能被 ...

  • 一次List对象去重失败,引发对Java8中distinct()的思考

    list的转map的另一种猜想 Java8使用lambda表达式进行函数式编程可以对集合进行非常方便的操作.一个比较常见的操作是将list转换成map,一般使用Collectors的toMap()方法 ...

  • 意大利布雷达机枪,供弹方式小日本直呼内行,子弹回收功能最鸡肋

    在我们国家,许多学校军训内容中,都有实弹打靶这个环节.学员三发子弹,老师十发子弹.那种扣住扳机不撒手,看着弹壳乱飞的感觉,对男人来说是一种享受.但如果你打完靶还要一枚弹壳做纪念,那大概率是不行的. 因 ...

  • 终下决心试奔驰G350 充满争议/但并不“鸡肋”

    毫无疑问,G350是奔驰品牌目前最具争议的产品,但平心而论,这款车其实并不鸡肋,对于手握200万左右预算又想买一辆大G的朋友来讲,奔驰G350其实正合适.导语 说起奔驰G350这车,上市已经有段时间了 ...