Optional 是 Java8 提供的为了解决 null 安全问题的一个 API。善用 Optional 可以使我们代码中很多繁琐、丑陋的设计变得十分优雅。
文章目录
1、Optional 类概述
1.1、Optional 类介绍
Java 8 引入的 Optional
类,是一个可以包含或不包含非空值的容器对象。它的目的是为了提供一种更优雅的方式来处理可能为 null
的值,从而避免直接使用 null
值可能引发的 NullPointerException
。
使用 Optional
类可以显著改善代码的可读性和健壮性。
Optional 类的主要特点:
- 防止
NullPointerException
:使用Optional
可以明确地要求用户在使用变量之前处理null
情况; - 增强代码可读性:通过使用
Optional
的方法,代码的可读性和意图更加明确; - 集成到 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
方法来转换 UserDO
到 UserVO
,而不需要手动检查 null
。如果 userDO
为 null
,map
方法不会执行其内部的 lambda 表达式,并且 getUserVOById
方法将返回 orElse
方法中所指定的默认值。
2、Java 8 中 Optional 类的主要方法
2.1、创建 Optional 对象
2.1.1、Optional.of(T value)
方法
Optional.of(T value)
:创建一个包含非空值的 Optional
对象。如果传入的 value
是 null
,则会立即抛出 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
对象。如果 value
为 null
,则返回一个空的 Optional
实例;如果 value
非 null
,则创建一个包含该值的 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
的链式调用,如 map
、flatMap
、filter
等,可以编写出更简洁、更声明式的代码。这种方式有助于提高代码的可读性和维护性。
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、变量值可能缺失的接口,代码清单如下: