本文已参与「掘力星计划」,赢取创作大礼包,挑战创作激励金。
前言
不管是一名小白程序员还是有三五年工作的CRUD BOY,或者是资深的高级Java工程师,在我们日常开发中,由于null的存在,经常会遇到NullPointerException
,而我们为了避免该异常的产生,往往需要对于使用到的对象进行一些非空判断,最开始我们都会使用if…else来进行处理,比如下面的代码:
1 | java复制代码Person person = getPersonById("123"); |
这种if判断的方式导致代码的阅读性和维护性都变差,并且很容易会忘记,导致出现BUG。
Java 8中推出了Optional<T>
,专门来解决这一问题。我们先来看看官方文档的介绍。
A container object which may or may not contain a non-null value. If a value is present, isPresent() will return true and get() will return the value.
Additional methods that depend on the presence or absence of a contained value are provided, such as orElse() (return a default value if value not present) and ifPresent() (execute a block of code if the value is present).
This is a value-based class; use of identity-sensitive operations (including reference equality (==), identity hash code, or synchronization) on instances of Optional may have unpredictable results and should be avoided.
通过官方文档我们可以看出,Optional<T>
是一个容器对象,容器中存放着一个值,这个值可能是空也有可能不是空,并且还提供了一系列依赖于值是否为空的方法;Optional<T>
是一个value-based类,也可以理解为基于值的类,所以像==,hash,synchronization这些操作应该避免,因为这些操作一般都是基于对象的空间地址而并非值。
方法
接下来我们看一下Optional<T>
都提供哪些方法。
创建Optional
Optional.empty()
返回一个空的 Optional
实例。
1 | java复制代码public static<T> Optional<T> empty() { |
Optional.of(T value)
返回具有 Optional
的当前非空值的Optional
1 | java复制代码public static <T> Optional<T> of(T value) { |
该方法传入对象必须不能为空,如果为空则会抛出空指针异常。
Optional.ofNullable(T value)
返回一个 Optional
指定值的Optional,如果非空,则返回一个空的 Optional
。
1 | java复制代码public static <T> Optional<T> ofNullable(T value) { |
ofNullable(T value)
和 of(T value)
唯一的区别是可以传入空对象,如果为空则同等于empty()
。
其他相关方法
isPresent
如果存在值返回 true
,否则为 false
。
1 | java复制代码public boolean isPresent() { |
ifPresent
如果存在值,则使用该值调用指定的消费者,否则不执行任何操作。
1 | java复制代码public void ifPresent(Consumer<? super T> consumer) { |
注意和isPresent()
方法区分。
orElse
如果值存在返回值,否则返回 other
。
1 | java复制代码public T orElse(T other) { |
orElseGet
返回值(如果存在),否则调用 other
并返回该调用的结果。
1 | java复制代码public T orElseGet(Supplier<? extends T> other) { |
orElseThrow
返回包含的值(如果存在),否则抛出由提供的exceptionSupplier
创建的异常。
1 | java复制代码public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X { |
map
将原Optional<T>
转换为Optional<U>
,如果值为空,则返回的也是空的Optional<U>
。
1 | java复制代码public<U> Optional<U> map(Function<? super T, ? extends U> mapper) { |
比如以下代码的目的是取出pOptional中person的address,如果person
为空,则返回的的是Optional.empty()
,反之则是Optional.ofNullable(person.getAddress())
:
1 | java复制代码Optional<Person> pOptional = Optional.ofNullable(getPersonById(123)); |
以上代码可简化为:
1 | java复制代码Optional<Address> add = pOptional.map(Person::getAddress); |
flatMap
从结果上看和map方法一样,都是将原Optional<T>
转换为Optional<U>
,就别在于方法参数中的mapper
,map()
方法的mapper
要求apply()
方法返回的是具体的值,而flatMap()
的mapper
参数的apply()
方法返回的就是一个Optional<U>
对象。
1 | java复制代码public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper) { |
上述map()
中的案例使用flatMap()
实现如下:
1 | java复制代码Optional<Address> address1 = pOptional.flatMap(new Function<Person, Optional<Address>>() { |
同样可以简写为:
1 | java复制代码pOptional.flatMap(person1 -> Optional.of(person1.getAddress())); |
filter
对值进行过滤,如果值存在并且在predicate
中返回为true
,返回Optional
本身,否则返回empty()
。
1 | java复制代码public Optional<T> filter(Predicate<? super T> predicate) { |
比如以下案例:
1 | java复制代码Optional<Person> pOptional = Optional.ofNullable(getPersonById(123)); |
简写为:
1 | java复制代码Optional<String> nameOpt = pOptional.filter(p -> p.getAge() > 10).map(Person::getName); |
那么通过上面的方法你是不是想立马在代码中对于你的if…else进行改造了呢?回顾我们最开始的代码:
1 | java复制代码Person person = getPersonById("123"); |
我们来用Optional
改造一下看看:
1 | java复制代码Person person = getPersonById("123"); |
是不是感觉代码看着好像“高大上”了,但是这样是正确的吗?在这个过程中其实创建了很多的Optional对象出来,起码在空间上是更浪费的。
Optional正确的使用方式应该是什么呢?
Optional使用原则
Java语言的架构师Brian Goetz明确定义:
Optional is intended 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
有时候我们学会了一个东西之后就像立马使用,看什么代码都想套进去,有个例子说:有了一把锤子后,看啥都像钉子。
总想去敲两下。比如以下代码:
避免:
1 | java复制代码public String fetchStatus() { |
而应该:
1 | java复制代码public String fetchStatus() { |
通过三目运算很简单的功能,非要用Optional
,不光可读性变差,还额外构建了一个Optional
对象。
不要将任何域对象声明为Optional
不要将任何方法的参数(尤其是setter)和构造方法的参数设置为Optional。
因为Optional不能被序列化,所以Optional设计时就没打算被用作对象的属性。
如果想通过Optional来保证对象的属性不被设置为null值,可以通过如下方式实现:
1 | java复制代码// PREFER |
可以看到这里getPostcode()
方法返回了一个Optional
,但是不要在你的代码里将所有getter都改成这样,尤其是返回集合或数组的情况,返回空集合和数组就可以了,因为集合本身就有empty()
方法可以判断是否为空。
在方法参数中使用Optionalin是另一个常见错误。这将导致代码的复杂度变高。应该在方法内部对参数进行检查,而不是强制调用方使用Optional
。
不要使用Optional替代集合返回值
通过返回空集合的形式表示方法没有返回结果,通过Collections.emptyList()
、emptyMap()
和emptySet()
构建空返回。
Optional值的比较使用equals
因为Optional
类是一个value-based
类,它的equals
方法本身就是比较值,所以如果你需要对两个Optional
中的值进行比较,则可以直接使用equals
方法。
1 | java复制代码public boolean equals(Object obj) { |
不要将Null值赋值为Optional变量
Optional.empty()
会初始化一个Optional
代替null
值,Optional
是一个容器,被定义为null
就没有意义。
避免以下代码:
1 | java复制代码public Optional<Cart> fetchCart() { |
而应该:
1 | java复制代码public Optional<Cart> fetchCart() { |
在调用 get() 之前确保 Optional 具有值
如果你需要调用get()方法获取Optional
值时,一定要确保在调用之前检查过Optional
中有值;通常都会使用isPresent()-get()
搭配的方式使用,但是这种方式并不优雅;但是你如果一定要选择使用get()
则不要忘记isPresent()
。
避免以下代码:
1 | java复制代码Optional<Cart> cart = ... ; // 可能返回一个空的Optional |
而应该:
1 | java复制代码if (cart.isPresent()) { |
值为空时,通过 orElse() 方法返回默认对象
这种方式比isPresent()-get()
方式更加优雅。但是这里有点小问题,就是即使Optional
有值时,也会执行orElse()方法,并且需要构建一个对象,所以性能上有一点小小的损耗。
避免以下代码:
1 | java复制代码public static final String USER_STATUS = "UNKNOWN"; |
而应该:
1 | java复制代码public static final String USER_STATUS = "UNKNOWN"; |
值为空时,通过 orElseGet() 方法返回默认对象
orElseGet()
是对isPresent()-get()
的另一种优雅替代方案,同时比orElse()
来说,要更加高效,因为orElseGet()
方法的参数是Java8中的Supplier
,只有Optional中的值为空时,才会执行Supplier
的get()方法,相对`orElse()没有性能损失。
避免以下代码:
1 | java复制代码public String computeStatus() { |
同样避免:
1 | java复制代码public String computeStatus() { |
而应该:
1 | java复制代码public String computeStatus() { |
总结
乍一看Optional
好像挺简单的,但是要想正确使用并不容易,OptionalAPI主要用于返回类型,清晰表达返回值中没有结果的可能性。
- 不要过度使用
Optional
Optional
尽量作为方法的返回值使用- 获取之前先判断是否有值
- 适当使用
Optional API
一些不恰当的使用可能导致在某些情况下出现BUG,建议收藏本文,使用Optional
时进行参考。
抽奖说明
- 本活动由掘金官方支持 详情可见掘力星计划。
- 欢迎在评论区讨论,掘金官方将在掘力星计划活动结束后,在评论区抽送100份掘金周边,抽奖详情见活动文章。
- 与文章内容相关的评论、建议、讨论等,「踩踩」「学习了」等泛泛类的评论无法获奖
讨论话题都给大家准备好了,思考一下,下图中的这几个人有什么问题,以及最后提交的代码是否有BUG,可以在留言区讨论。
本文转载自: 掘金