• 详解 Optional 的使用,优雅的解决空指针异常


    1. 概述

    我们在编写代码的时候出现最多的就是空指针异常,所以在很多情况下我们需要做各种非空的判断。

    1. Author author = geAuthor();
    2. if(author != null){
    3. System.out.println(author.getName());
    4. }
    5. 复制代码

    尤其是对象的属性还是一个对象的情况下,这种判断会更多,而过多的判断语句会让我们的代码显得臃肿不堪。

    所以在 JDK8 中引入了 Optional 来解决空指针异常的问题,养成使用 Optional 的习惯后你可以写出更优雅的代码来避免空指针异常。并且在很多函数式编程相关的 API 中也都用到了 Optional ,学习 Optional 也对我们使用函数式编程有益。

    2. 使用

    2.1. 创建对象

    Optional 就好像是一个包装类,可以把具体的数据封装到 Optional 对象内部,然后我们去使用 Optional 中封装好的方法操作封装进去的数据就可以非常优雅的避免空指针异常。

    2.1.1. ofNullable 方法

    我们一般使用 Optional 的静态方法 ofNullable 来把数据封装成一个 Optional对象,无论传入的参数是否为 null 都不会出现问题。

    分析一下源码,当你传入一个对象时会判断是否为空,如果为空就调用 empty 方法,不为空调用 of 方法,of方法稍后会讲到。

    empty 静态方法相当于返回一个空的 Optional 对象

    你可能会觉得还要加一行代码来封装数据比较麻烦,但是如果改造下 getAuthor 方法,让其返回值就是封装好的 Optional 对象的话,我们在使用的时候就会方便很多。

    而且在实际开发中我们的数据很多是从数据库获取的。MyBatis3.5 以上版本也已经支持 Optional 了。我们可以直接把 Dao 层方法的返回值类型定义成 Optional 类型, MyBatis 会自己把数据封装成 Optional 对象进行返回,封装的过程也不需要我们自己操作。

    2.1.2. of 方法

    如果你确定一个对象不为空则可以使用 Optional 的静态方法 of 来把数据封装成 Optional 对象。

    1. Author author = new Author;
    2. Optional<Author> authorOptional = Optional.of(author);
    3. 复制代码

    但是一定要注意,如果使用 of 方法的时候传入的参数必须不为 null 。如果为 null,则会报空指针异常。


    如果一个方法的返回值类型是 Optional 类型。当我们在方法计算某次得到的返回值为 null 时,这个时候就需要把 null 封装成 Optional 对象返回。这个时候可以使用 Optional 的静态方法 empty 来进行封装。

    empty 静态方法相当于返回一个空的 Optional 对象

    注意

    使用ofNullable 方法,当传入 null 时,也会使用 empty 方法返回一个空的 Optional 方法,所以推荐使用 ofNullable 方法而不是 empty 方法,这样省的我们再判断对象是否为空了,ofNullable 已经帮我们做了判空操作。

    2.2. 安全的消费值

    我们获取到一个 Optional 对象后肯定需要对其中的数据进行使用,这个时候我们可以使用 ifPresent 方法来消费其中的值。

    1. private static void testFilter() {
    2. Optional<Author> authorOptional = getAuthorOptional();
    3. authorOptional.ifPresent(new Consumer<Author>() {
    4. @Override
    5. public void accept(Author author) {
    6. System.out.println(author.getName());
    7. }
    8. });
    9. //lambda表达式的写法
    10. authorOptional.ifPresent(author -> System.out.println(author.getName()));
    11. }
    12. public static Optional<Author> getAuthorOptional(){
    13. Author author = new Author(1L,"蒙多",33,"一个从菜刀中明悟哲理的祖安人",null);
    14. //书籍列表
    15. List<Book> books1 = new ArrayList<>();
    16. books1.add(new Book(1L,"刀的两侧是光明与黑暗","哲学,爱情",88,"用一把刀划分了爱恨"));
    17. books1.add(new Book(2L,"一个人不能死在同一把刀下","个人成长,爱情",99,"讲述如何从失败中明悟真理"));
    18. author.setBooks(books1);
    19. return Optional.ofNullable(author);
    20. }
    21. 复制代码

    这个方法会判断其封装的数据是否为空(value 表示封装在 Optional 的对象),不为空时才会执行具体的消费代码(我们实现的 accpet 方法),这样使用起来就更加安全了。

    2.3. 安全的获取值

    2.3.1. 不推荐的做法

    当我们想要获取 Optional 封装的对象自己进行处理可以使用 Optional 对象提供的 get 方法进行获取,但是不推荐。因为当 Optional 内部的数据为空的时候会抛出异常。

    2.3.2. 推荐的做法

    如果我们期望安全的获取值,不推荐使用 get 方法,而是使用 Optional 提供的以下方法:

    1. orElseGet 方法:获取数据并且设置数据为空时的默认值。如果数据不为空就能获取到该数据,如果为空则根据你传入的参数创建对象作为默认值返回。

    分析一下源码,当使用 orElseGet 方法获取值时,会进行判断。如果值为空,则返回我们实现Supplier 接口中的 get 方法的返回值(默认值);如果不为空返回真正的值。

    1. orElse 方法:获取数据时直接传入默认值,当数据为空时返回默认值。

    比较一下 orElseGetorElseorElseGet 方法需要实现 get 方法,其返回值作为默认值,而 orElse 更直接,直接传入值作为默认值。但是通过 orElseGet 方法我们可以进行一些处理返回默认值。两者的使用情况可以根据使用场景来决定。

    1. orElseThrow方法:获取数据,如果数据不为空就能获取到数据,如果为空则根据你传入的参数来抛出异常。

    分析一下源码,与 orElseThrow 方法实现逻辑一致,不同的是Supplier接口中的 get方法返回得是一个异常对象,当值为空时,会抛出这个异常对象。

    为什么有orElseThrow这个方法呢?

    • 比如我们在使用 Spring 框架的时候,我们会进行异常的统一处理,使用 orElseThrow 方法,当值为空时抛出异常,统一异常处理器捕获到异常,封装成特定信息,返回给前端,便于我们的控制和扩展。

    2.4. 过滤

    我们使用 filter 方法对数据进行过滤。如果原本是有数据的,但是过滤后没有了数据,会返回一个无数据的 Optional 对象。

    分析一下源码

    Optional 对象中的 filter 方法不同于流中的 filter 方法。流中的 filter 方法是针对流中的每一个元素判断是否满足条件进行过滤。Optional 对象的 filter 首先会判断传入的实现是否为空,如果为空就抛出异常。

    然后通过 isPresent 方法在过滤之前判断该 Optional 对象中是否存在数据,如果不存在就返回这个空的 Optional 对象。

    然后通过我们实现的 test 方法判断值是否满足条件,满足返回该 Optional对象,不满足返回空的 Optional 对象。

    2.5. 数据转换

    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 方法进行包装,都不会触发 flatMaprequireNonNull 判断值为空异常,因为通过 ofNullableof 方法包装的对象返回的是一个 Optional 对象。

    但是我觉得还是使用 map 吧,大家有什么想法也可以在评论区发表一下......

    3. 总结

    Optional 帮我们优雅的解决了 Java 的空指针异常问题,让我们的程序更健壮。经过上述的分析,Optional 的使用应该不难,我们只要记住如何使用 Optional 创建对象,安全的消费值、获取值,就能在大多数场景使用了。在源码分析中,我们发现 Optional API 设计的非常健壮,涵盖了多数场景,这也会提高我们的编码能力。

  • 相关阅读:
    Spring 系列(三):你真的懂@RequestMapping吗?
    Kafka常见问题解析
    10月8日 Jdbc(1)
    leetcode-19-回溯-组合问题(剪枝、去重)
    前沿科技分论坛精彩回顾 | 第二届始祖数字化可持续发展峰会
    Hundred Finance 攻击事件分析
    用python把docx批量转为pdf
    Windows桌面自动化测试工具:WinAppDriver
    【JavaScript高级】内存管理与闭包:垃圾回收GC、闭包定义、访问和执行过程、内存泄漏
    vagrant+virtualbox的踩坑记录
  • 原文地址:https://blog.csdn.net/m0_73311735/article/details/127407235