一文掌握 Java8 的 Optional 的 6 种操作
你好,我是看山。
Java8 中引入了一个特别有意思类:Optional
,一个可以让我们更加轻松的避免 NPE(空指针异常,NullPointException)的工具。
很久很久以前,为了避免 NPE,我们会写很多类似if (obj != null) {}
的代码,有时候忘记写,就可能出现 NPE,造成线上故障。在 Java 技术栈中,如果谁的代码出现了 NPE,有极大的可能会被笑话,这个异常被很多人认为是低级错误。Optional
的出现,可以让大家更加轻松的避免因为低级错误被嘲讽的概率。
定义示例数据
先定义待操作对象,万能的Student
类和Clazz
类(用到了 lombok 和 guava):
然后定义一组测试数据:
创建实例:of、ofNullable
为了控制生成实例的方式,也是为了收紧空值Optional
的定义,Optional
将构造函数定义为private
。想要创建Optional
实例,可以借助of
和ofNullable
两个方法实现。
这两个方法的区别在于:of
方法传入的参数不能是null
的,否则会抛出NullPointerException
。所以,对于可能是null
的结果,一定使用ofNullable
。
代码如下:
Optional
类中还有一个静态方法:empty
,这个方法直接返回了内部定义的一个常量Optional<?> EMPTY = new Optional<>()
,这个常量的value
是null
。ofNullable
方法也是借助了empty
实现null
的包装:
所以说,对于null
的Optional
包装类,指向的都是相同的实例对象,Optional.empty() == Optional.ofNullable(null)
返回的是true
。换句话说,空Optional
是单例的。
为了方便描述,下文中对值为
null
的Optional
统称为“空Optional
”。
获取数据:get
Optional
的get
方法有些坑人,先看下它的源码:
也就是说,Optional
值为空时,使用get
方法将抛出NoSuchElementException
异常。如果不想抛出异常,或者能够 100%确定不是空Optional
,或者使用isPresent
方法判断。
如果能 100%确定不是空Optional
,那就没有必要使用Optional
包装,直接返回即可。如果需要使用isPresent
方法,那就和直接判空没有区别了。所以,无论是第一种情况还是第二种情况,都违背了设计这个类的初衷。
值为空判断:isPresent、ifPresent
isPresent
用来判断值是否为空,类似于obj != null
,ifPresent
可以传入一个Consumer
操作,当值不为空的时候,会执行Consumer
函数。比如:
上面的方法等价于:
isPresent
判断的写法上是不是感觉很熟悉,感觉可以直接写为:
对于isPresent
,如果是在自己可控的代码范围内,完全没有必要将值封装之后再判空。对于自己不可控的代码,后续的filter
或者map
方法可能比isPresent
更好用一些。
对于ifPresent
,在使用的时候会有一些限制,就是必须是非空Optional
的时候,在会执行传入的Consumer
函数。
值处理:map、flatMap
map
和flatMap
是对Optional
的值进行操作的方法,区别在于,map
会将结果包装到Optional
中返回,flatMap
不会。但是两个方法返回值都是Optional
类型,这也就要求,flatMap
的方法函数返回值需要是Optional
类型。
我们来看看map
的实现:
可以看到,如果Optional
的值为空,map
直接返回Optional.EMPTY
,否则会执行函数结果,并使用Optional.ofNullable
包装并返回。也即是说,只要类结构允许,我们可以一直map
下去,就像是扒洋葱,一层一层,直到核心。
比如,我们要获取s2
所在班级名称,在定义的时候,我们将s2
的clazz
属性定义为 null,如果以前需要写为:
现在借助Optional
可以写为:
从代码上似乎没有多大改变,但是如果Clazz
内部还有类对象。或者,我们在if
判断的时候,少写一层检查呢?而且,map
的精巧还在于它的返回值永远是Optional
,这样,我们可以重复调用map
方法,而不需要中间被打断,增加各种判空逻辑。
值为空的处理:orElse、orElseGet、orElseThrow
这几个方法可以与map
操作结合,一起完成对象操作。当值为空时,orElse
和orElseGet
返回默认值,orElseThrow
抛出指定的异常。
orElse
和orElseGet
的区别是,orElse
方法传入的参数是明确的默认值,orElseGet
方法传入的参数是获取默认值的函数。如果默认值的构造过程比较复杂,需要经过一系列的运算逻辑,那一定要使用orElseGet
,因为orElseGet
是在值为空的时候,才会执行函数,并返回默认值,如果值不为空,则不会执行函数,相比于orElse
而言,减少了一次构造默认值的过程。
同样以上面的例子:
orElse
的写法:
orElseGet
的写法:
如果clazz
属性一定不为空,为空则返回异常,可以使用orElseThrow
:
条件过滤:filter
filter
方法提供的是值验证,如果值验证为 true,返回当前值;否则,返回空Optional
。比如,我们要遍历students
,找到班级属性为空的,打印学生 id:
其他:equals、hashCode、toString
Optional
重写了这三个方法。因为Optional
可以认为是包装类,所以还是围绕这被包装的值重写这三个方法。下面给出这三个方法的源码:
equals
方法,Optional.of(s1).equals(Optional.of(s2))
完全等价于s1.equals(s2)
。
hashCode
方法,直接返回的是值的 hashCode,如果是空Optional
,返回的是 0。
toString
方法,为了能够识别是Optional
,将打印数据包装了一下。如果是空Optional
,返回的是字符串“Optional.empty”;如果是非空,返回是是“Optional[值的 toString]”。
文末总结
NPE 之所以讨厌,就是只要出现 NPE,我们就能够解决。但是一旦出现,都已经是事后,可能已经出现线上故障。偏偏在 Java 语言中,NPE 又很容易出现。Optional
提供了模板方法,有效且高效的避免 NPE。
接下来,我们针对上面的使用,总结一下:
Optional
是一个包装类,且不可变,不可序列化没有公共构造函数,创建需要使用
of
、ofNullable
方法空
Optional
是单例,都是引用Optional.EMPTY
想要获取
Optional
的值,可以使用get
、orElse
、orElseGet
、orElseThrow
另外,还有一些实践上的建议:
使用
get
方法前,必须使用isPresent
检查。但是使用isPresent
前,先思考下是否可以使用orElse
、orElseGet
等方法代替实现。orElse
和orElseGet
,优先选择orElseGet
,这个是惰性计算Optional
不要作为参数或者类属性,可以作为返回值尽量将
map
、filter
的函数参数抽出去作为单独方法,这样能够保持链式调用
推荐阅读
版权声明: 本文为 InfoQ 作者【看山】的原创文章。
原文链接:【http://xie.infoq.cn/article/237c8f985cbb4bbe479bc241d】。文章转载请联系作者。
评论