我们在编写代码的时候出现最多的就是空指针异常,所以在很多情况下我们需要做各种非空的判断。
Author author = geAuthor();
if(author != null){
System.out.println(author.getName());
}
复制代码
尤其是对象的属性还是一个对象的情况下,这种判断会更多,而过多的判断语句会让我们的代码显得臃肿不堪。
所以在 JDK8
中引入了 Optional
来解决空指针异常的问题,养成使用 Optional
的习惯后你可以写出更优雅的代码来避免空指针异常。并且在很多函数式编程相关的 API
中也都用到了 Optional
,学习 Optional
也对我们使用函数式编程有益。
Optional
就好像是一个包装类,可以把具体的数据封装到 Optional
对象内部,然后我们去使用 Optional
中封装好的方法操作封装进去的数据就可以非常优雅的避免空指针异常。
ofNullable
方法我们一般使用 Optional
的静态方法 ofNullable
来把数据封装成一个 Optional
对象,无论传入的参数是否为 null
都不会出现问题。
分析一下源码,当你传入一个对象时会判断是否为空,如果为空就调用 empty
方法,不为空调用 of
方法,of
方法稍后会讲到。
empty
静态方法相当于返回一个空的 Optional
对象
你可能会觉得还要加一行代码来封装数据比较麻烦,但是如果改造下 getAuthor
方法,让其返回值就是封装好的 Optional
对象的话,我们在使用的时候就会方便很多。
而且在实际开发中我们的数据很多是从数据库获取的。MyBatis3.5
以上版本也已经支持 Optional
了。我们可以直接把 Dao
层方法的返回值类型定义成 Optional
类型, MyBatis
会自己把数据封装成 Optional
对象进行返回,封装的过程也不需要我们自己操作。
of
方法如果你确定一个对象不为空则可以使用 Optional
的静态方法 of
来把数据封装成 Optional
对象。
Author author = new Author;
Optional authorOptional = Optional.of(author);
复制代码
但是一定要注意,如果使用 of
方法的时候传入的参数必须不为 null
。如果为 null
,则会报空指针异常。
如果一个方法的返回值类型是 Optional
类型。当我们在方法计算某次得到的返回值为 null
时,这个时候就需要把 null
封装成 Optional
对象返回。这个时候可以使用 Optional
的静态方法 empty
来进行封装。
empty
静态方法相当于返回一个空的 Optional
对象
注意
使用ofNullable
方法,当传入 null
时,也会使用 empty
方法返回一个空的 Optional
方法,所以推荐使用 ofNullable
方法而不是 empty
方法,这样省的我们再判断对象是否为空了,ofNullable
已经帮我们做了判空操作。
我们获取到一个 Optional
对象后肯定需要对其中的数据进行使用,这个时候我们可以使用 ifPresent
方法来消费其中的值。
private static void testFilter() {
Optional authorOptional = getAuthorOptional();
authorOptional.ifPresent(new Consumer() {
@Override
public void accept(Author author) {
System.out.println(author.getName());
}
});
//lambda表达式的写法
authorOptional.ifPresent(author -> System.out.println(author.getName()));
}
public static Optional getAuthorOptional(){
Author author = new Author(1L,"蒙多",33,"一个从菜刀中明悟哲理的祖安人",null);
//书籍列表
List books1 = new ArrayList<>();
books1.add(new Book(1L,"刀的两侧是光明与黑暗","哲学,爱情",88,"用一把刀划分了爱恨"));
books1.add(new Book(2L,"一个人不能死在同一把刀下","个人成长,爱情",99,"讲述如何从失败中明悟真理"));
author.setBooks(books1);
return Optional.ofNullable(author);
}
复制代码
这个方法会判断其封装的数据是否为空(value
表示封装在 Optional
的对象),不为空时才会执行具体的消费代码(我们实现的 accpet
方法),这样使用起来就更加安全了。
当我们想要获取 Optional
封装的对象自己进行处理可以使用 Optional
对象提供的 get
方法进行获取,但是不推荐。因为当 Optional
内部的数据为空的时候会抛出异常。
如果我们期望安全的获取值,不推荐使用 get
方法,而是使用 Optional
提供的以下方法:
orElseGet
方法:获取数据并且设置数据为空时的默认值。如果数据不为空就能获取到该数据,如果为空则根据你传入的参数创建对象作为默认值返回。分析一下源码,当使用 orElseGet
方法获取值时,会进行判断。如果值为空,则返回我们实现Supplier
接口中的 get
方法的返回值(默认值);如果不为空返回真正的值。
orElse
方法:获取数据时直接传入默认值,当数据为空时返回默认值。比较一下 orElseGet
和 orElse
:orElseGet
方法需要实现 get
方法,其返回值作为默认值,而 orElse
更直接,直接传入值作为默认值。但是通过 orElseGet
方法我们可以进行一些处理返回默认值。两者的使用情况可以根据使用场景来决定。
orElseThrow
方法:获取数据,如果数据不为空就能获取到数据,如果为空则根据你传入的参数来抛出异常。分析一下源码,与 orElseThrow
方法实现逻辑一致,不同的是Supplier
接口中的 get
方法返回得是一个异常对象,当值为空时,会抛出这个异常对象。
为什么有
orElseThrow
这个方法呢?
Spring
框架的时候,我们会进行异常的统一处理,使用 orElseThrow
方法,当值为空时抛出异常,统一异常处理器捕获到异常,封装成特定信息,返回给前端,便于我们的控制和扩展。我们使用 filter
方法对数据进行过滤。如果原本是有数据的,但是过滤后没有了数据,会返回一个无数据的 Optional
对象。
分析一下源码
Optional
对象中的 filter
方法不同于流中的 filter
方法。流中的 filter
方法是针对流中的每一个元素判断是否满足条件进行过滤。Optional
对象的 filter
首先会判断传入的实现是否为空,如果为空就抛出异常。
然后通过 isPresent
方法在过滤之前判断该 Optional
对象中是否存在数据,如果不存在就返回这个空的 Optional
对象。
然后通过我们实现的 test
方法判断值是否满足条件,满足返回该 Optional
对象,不满足返回空的 Optional
对象。
Optional
还提供了 map
可以让我们对数据进行转换,并且转换得到的数据还是被 Optional
包装好的,保证了我们的使用安全。map
相当于一种映射。
例如我们获取作家的书籍集合:
分析一下源码:
我们调用 map
方法,首先会通过 Objects.requireNonNull
方法判断是否实现了 Function
接口,然后再通过 isPresent
方法判断 Optional
对象中是否有值,没有值的话,就调用 empty
方法返回一个空的 Optional
对象;有值的话,就执行我们实现的 apply
返回一个由 ofNullable
包装的 Optional
对象。
我们发现还有一个
flatMap
方法,我们使用一下它看看有什么作用?
通过 flatMap
取出作者的年龄:
通过 map
取出作者的年龄:
你会发现 flatMap
方法的返回值需要进行包装,而 map
方法的不需要。
我们来对比一下两者的源码:
两者都先通过 Objects.requireNoNull
方法判断,是否实现了 Function
接口,然后再通过 isPresent
判断对象中是否有值。不同的是 map
方法执行 apply()
方法后,会将返回值通过 ofNullable
包装,而 flatMap
方法执行 apply()
方法后,使用 requireNoNull
方法判断返回值是否为空,为空则抛出异常,不为空返回原值,没有经过 Optional
包装;但是 flatMap
方法的返回值是一个 Optional
类型,那么需要我们在实现 apply
方法时,对处理后的值包装为 Optional
对象。
使用
map
还是flatMap
方法那个更合适呢?
说实话这两个方法使用起来没什么大的区别,就是 flatMap
的实现 apply
方法需要我们包装为 Optional
对象。但是有没有一种可能,你使用 map
方法时,将返回值包装为了 Optional
对象,那么就嵌套了好几层 Optional
,但是在强大 IDE
的支持下,这也不太可能。但是你使用 flatmap
,就会规避这种风险,但是你需要手动包装 apply
方法的返回值,且应该使用 ofNullable
方法进行包装,因为使用 of
包装当值为 null
时,会抛出异常。
不管使用 ofNullable
还是 of
方法进行包装,都不会触发 flatMap
的 requireNonNull
判断值为空异常,因为通过 ofNullable
和 of
方法包装的对象返回的是一个 Optional
对象。
但是我觉得还是使用 map
吧,大家有什么想法也可以在评论区发表一下......
Optional
帮我们优雅的解决了 Java
的空指针异常问题,让我们的程序更健壮。经过上述的分析,Optional
的使用应该不难,我们只要记住如何使用 Optional
创建对象,安全的消费值、获取值,就能在大多数场景使用了。在源码分析中,我们发现 Optional API
设计的非常健壮,涵盖了多数场景,这也会提高我们的编码能力。