【Java 新特性系列】Java 8 Optional 类完全指南

avatar
作者
猴君
阅读量:2

Optional 是 Java8 提供的为了解决 null 安全问题的一个 API。善用 Optional 可以使我们代码中很多繁琐、丑陋的设计变得十分优雅。


文章目录


1、Optional 类概述
1.1、Optional 类介绍

Java 8 引入的 Optional 类,是一个可以包含或不包含非空值的容器对象。它的目的是为了提供一种更优雅的方式来处理可能为 null 的值,从而避免直接使用 null 值可能引发的 NullPointerException

使用 Optional 类可以显著改善代码的可读性和健壮性。

Optional 类的主要特点:

  1. 防止 NullPointerException:使用 Optional 可以明确地要求用户在使用变量之前处理 null 情况;
  2. 增强代码可读性:通过使用 Optional 的方法,代码的可读性和意图更加明确;
  3. 集成到 Java 的流 API 中:Optional 类型在 Java 8 的流(Stream)操作中被广泛使用,提供了更复杂的条件查询和变换功能。
1.2、使用 Optional 的前后对比

假设有一个用户信息管理系统,其中用户的 DO 包含多个字段,而前端只需要显示其中的几个字段。我们将从数据库获取用户详细信息,并转换为用户的 VO。

1.2.1、不使用 Optional

不使用 Optional 的情况下,代码可能直接处理可能的 null 值:

public UserVO getUserVOById(Long userId) {     UserDO userDO = userMapper.selectById(userId);  // MyBatis 的查询     if (userDO == null) {         return null;     }     UserVO userVO = new UserVO();     userVO.setName(userDO.getName());     userVO.setEmail(userDO.getEmail());     // 更多字段赋值...     return userVO; }  
1.2.2、使用 Optional

使用 Optional,你可以优雅地处理 null 值,并且代码更具表达性:

public UserVO getUserVOById(Long userId) {     Optional<UserDO> userDOOptional = Optional.ofNullable(userMapper.selectById(userId));     return userDOOptional.map(userDO -> {         UserVO userVO = new UserVO();         userVO.setName(userDO.getName());         userVO.setEmail(userDO.getEmail());         // 更多字段赋值...         return userVO;     // 如果有值则返回该值,否则返回 null     }).orElse(null); } 

在这个例子中,selectById 方法的返回值被包装在一个 Optional 中。这使得可以直接使用 map 方法来转换 UserDOUserVO,而不需要手动检查 null。如果 userDOnullmap 方法不会执行其内部的 lambda 表达式,并且 getUserVOById 方法将返回 orElse 方法中所指定的默认值。


2、Java 8 中 Optional 类的主要方法
2.1、创建 Optional 对象
2.1.1、Optional.of(T value) 方法

Optional.of(T value):创建一个包含非空值的 Optional 对象。如果传入的 valuenull,则会立即抛出 NullPointerException。这个方法用于包装那些确定不为 null 的值。

Optional<String> opt = Optional.of("Hello"); 
2.1.2、Optional.empty() 方法

Optional.empty():创建一个空的 Optional 实例。这是用来表示没有值的情况,通常用于初始化或在条件分支中返回一个明确的“无值”状态。

Optional<String> opt = Optional.empty(); 
2.1.3、Optional.ofNullable(T value) 方法

Optional.ofNullable(T value):根据传入的 value 创建一个 Optional 对象。如果 valuenull,则返回一个空的 Optional 实例;如果 valuenull,则创建一个包含该值的 Optional 对象。这个方法用于可能为 null 的情况,提供了一种安全的方式来包装 null 值。

Optional<String> opt = Optional.ofNullable(null); 
2.2、条件动作
2.2.1、isPresent() 方法

isPresent():检查 Optional 是否包含值。如果包含值,则返回 true;否则返回 false。这个方法通常在需要基于 Optional 中是否有值来执行不同操作的场合使用。

Optional<String> opt = Optional.of("Hello"); if (opt.isPresent()) {     System.out.println("Value is present."); } else {     System.out.println("Value is not present."); } 
2.2.2、ifPresent(Consumer<? super T> consumer) 方法

ifPresent(Consumer<? super T> consumer):如果 Optional 对象包含值,则执行给定的 consumer 操作。这个方法常用于在值存在的情况下执行某些操作,而无需进行显式的空检查。

Optional<String> opt = Optional.of("Hello"); opt.ifPresent(name -> System.out.println("Name is " + name)); 
2.3、值获取
2.3.1、orElse(T other) 方法

orElse(T other):如果有值则返回该值,否则返回 other。这个方法用于处理 Optional 对象可能为空的情况,提供一个默认值。

String name = Optional.ofNullable(null).orElse("Default Name"); System.out.println(name);  // 输出 "Default Name" 
2.3.2、get() 方法

get():如果 Optional 对象包含值,则返回此值;否则抛出 NoSuchElementException。使用 get() 方法前应该先检查是否有值(例如使用 isPresent()),或者使用捕获异常的方式来处理可能的 NoSuchElementException

Optional<String> opt = Optional.of("Hello"); if (opt.isPresent()) {     System.out.println(opt.get());  // 输出 "Hello" } else {     System.out.println("No value present"); } 
2.3.3、orElseGet(Supplier<? extends T> supplier) 方法

orElseGet(Supplier<? extends T> supplier):如果有值则返回该值,否则执行 supplier 提供的备选的生成策略,返回生成的值。这个方法通常用于延迟计算默认值或获取值的成本较高时。

String name = Optional.ofNullable(null).orElseGet(() -> {     // Perform some expensive operation or default value computation     return "Computed Default Name"; }); System.out.println(name);  // 输出 "Computed Default Name" 
2.3.4、orElseThrow(Supplier<? extends X> exceptionSupplier) 方法

orElseThrow(Supplier<? extends X> exceptionSupplier):如果 Optional 对象包含值,则返回此值;如果没有值,抛出由 exceptionSupplier 提供的异常。这使得用户可以定义缺失值时抛出的异常类型。

String result = Optional.ofNullable(null).orElseThrow(() -> new IllegalStateException("Value not present")); // 将抛出 IllegalStateException 

通过以上方法,Optional 提供了灵活的机制来从可能为空的对象中安全地获取值,每种方法适用于不同的应用场景,从而使代码更加健壮且易于维护。

2.4、转换和过滤
2.4.1、map(Function<? super T, ? extends U> mapper) 方法

map(Function<? super T, ? extends U> mapper):如果有值,应用提供的映射函数 mapper,并返回一个 Optional 对象来包含映射函数的结果。如果原 Optional 为空,则仍返回一个空的 Optional

Optional<String> opt = Optional.of("hello"); Optional<String> upper = opt.map(String::toUpperCase); upper.ifPresent(System.out::println);  // 输出 "HELLO" 

通过这些方法,Optional 类为 Java 开发者提供了一种强大的工具来处理可为空的情况,增强了程序的健壮性和可读性。

2.4.2、flatMap(Function<? super T, Optional<U>> mapper) 方法

flatMap(Function<? super T, Optional<U>> mapper):如果 Optional 对象有值,对其值应用提供的 mapper 函数,该函数必须返回 Optional 类型的结果。此方法用于避免嵌套 Optional(即 Optional<Optional<T>>)的情况,使结果保持在单一层级的 Optional

Optional<String> optionalString = Optional.of("hello"); Optional<Integer> length = optionalString.flatMap(s -> Optional.of(s.length())); length.ifPresent(System.out::println);  // 输出 5 
2.4.3、filter(Predicate<? super T> predicate) 方法

filter(Predicate<? super T> predicate):如果 Optional 对象有值,并且该值满足提供的 predicate 条件,则返回包含该值的 Optional;如果不满足条件,则返回一个空的 Optional。这使得 Optional 可以集成更复杂的条件逻辑,提供类似流中的过滤功能。

Optional<String> optionalString = Optional.of("hello"); Optional<String> longString = optionalString.filter(s -> s.length() > 3); longString.ifPresent(System.out::println);  // 输出 "hello"  Optional<String> shortString = optionalString.filter(s -> s.length() < 3); shortString.ifPresent(System.out::println);  // 不输出任何内容 

通过 map, flatMap, 和 filter 方法,Optional 类为 Java 开发者提供了类似于 Java Stream API 的强大的转换和过滤工具,但针对单个可能为空的值。这些方法增强了对 Optional 对象的处理能力,使代码能够以声明性和函数式的方式来处理数据,进一步减少了错误和异常的可能性,特别是与 null 相关的错误。


3、Optional 类使用时的注意事项

使用 Optional 类时,尽管它提供了很多便利,但也有一些注意事项和最佳实践需要遵循,以确保代码的健康性和性能。以下是使用 Optional 时的主要注意事项:

3.1、不要在类的字段中使用 Optional

使用 Optional 作为类的字段通常不推荐,因为 Optional 旨在作为方法的返回类型,用于有效地表示可空的结果。使用它作为字段类型会增加内存开销,同时也违反了其用作临时包装器的设计初衷。

3.2、避免使用 Optional 作为参数

Optional 用作方法参数通常是多余的。这种做法迫使调用者使用 Optional,而不是允许他们以更自然的方式传递值或 null。更好的方法是允许传递 null 并在方法内部使用 Optional.ofNullable() 进行处理。

3.3、不要仅为了避免 null 检查而使用 Optional

Optional 的过度使用可能导致代码质量下降,特别是当它被用来包装几乎从不为 null 的值时。它应该用在真正可能为空的情况,否则会导致不必要的复杂性。

3.4、使用 Optional 的链式调用

利用 Optional 的链式调用,如 mapflatMapfilter 等,可以编写出更简洁、更声明式的代码。这种方式有助于提高代码的可读性和维护性。

3.5、避免在 Optional 上进行显式的 null 检查

Optional 的目的是为了消除代码中的 null 检查。对 Optional 对象本身进行 null 检查是没有必要的,也违背了使用 Optional 的初衷。

3.6、在使用 get() 方法之前总是检查是否有值

直接调用 Optional.get() 而不先检查是否有值,可能会引发 NoSuchElementException。应该使用 isPresent() 或更好的方法是 orElse(), orElseGet(), 或 orElseThrow() 等,以更安全的方式访问值。

3.7、利用 Optional 提供的方法避免抛出异常

在可能的情况下,优先使用 orElse()orElseGet() 而不是 orElseThrow(),除非你确实需要在没有值的情况下抛出一个异常。这样做可以使得程序的控制流更加清晰。

3.8、 理解 Optional 和性能的关系

虽然 Optional 在编码安全性上提供了很多好处,但它也有一定的性能开销,因为每次使用 Optional 都涉及到创建新的对象实例。在性能敏感的应用中,滥用 Optional 可能会导致问题。

总之,Optional 是一个非常有用的工具,能够帮助Java开发者写出更清洁、更健壮的代码。然而,像任何工具一样,它需要在适当的情况下正确使用。理解何时以及如何使用 Optional,可以避免许多常见的编程错误,并最大化其优势。

3.9、orElse方法的使用

orElse 中调用的方法一直都会被执行,orElseGet 方法只有在 Optional 对象不含值时才会被调用,所以使用 orElse 方法时需要谨慎, 以免误执行某些不被预期的操作。此种情况下,可使用 orElseGet 方法代替它。

3.10、避免使用基础类型的 Optional 对象

Optional 提供了的一些基础类型 —— OptionalInt、OptionalLong 以及 OptionalDouble ,但不推荐大家使用基础类型的 Optional,因为基础类型的 Optional 不支持 map、 flatMap 以及 filter 方法,而这些却是 Optional 类常用的方法。可以使用 Optional, Optional, Optional 等替代

3.11、Optional的序列化问题

由于 Optiona l类设计时就没特别考虑将其作为类的字段使用,所以它也并未实现 Serializable 接口。由于这个原因,如果你的应用使用了某些要求序列化的库或者框架,在域模型中使用Optional,有可能引发应用程序故障。

然而,我们相信,通过前面的介绍,我们已经看到用 Optional 声明域模型中的某些类型是个不错的主意,尤其是你需要遍历有可能全部或部分为空,或者可能不存在的对象时。如果你一定要实现序列化的域模型,作为替代方案, 我们建议你像下面这个例子那样,提供一个能访问声明为 Optional、变量值可能缺失的接口,代码清单如下:

广告一刻

为您即时展示最新活动产品广告消息,让您随时掌握产品活动新动态!