1152 字
6 分钟

Java Optional 使用指南

发布于 2025-11-22

引言:十亿美元的错误#

在 Java 开发中,NullPointerException (NPE) 无疑是最令人头疼的异常之一。Tony Hoare 曾将空引用称为“十亿美元的错误”。为了解决这个问题,Java 8 引入了 java.util.Optional 类。

Optional 用来明确表达“值可能不存在”,并提供 mapfilterorElse 等 API 处理空值,减少层层嵌套的 null 判断。


1. 什么是 Optional?#

Optional<T> 是一个容器对象,它可能包含一个非空值,也可能不包含任何值(即为空)。

  • 如果有值,isPresent() 返回 true,调用 get() 返回该值。
  • 如果没有值,它就是一个空的 Optional 对象。

2. 创建 Optional 对象#

创建 Optional 实例主要有三种方式:

2.1 Optional.of(T value)#

用于创建一个非空的 Optional。如果传入 null,会立即抛出 NullPointerException

String name = "Java";
Optional<String> opt = Optional.of(name); // 正常
// Optional<String> error = Optional.of(null); // 抛出 NPE

2.2 Optional.ofNullable(T value)#

这是最常用的方式。如果传入的值不为 null,创建包含该值的 Optional;如果为 null,创建一个空的 Optional

String name = null;
Optional<String> opt = Optional.ofNullable(name); // 创建一个空 Optional,不会报错

2.3 Optional.empty()#

直接创建一个空的 Optional 对象。

Optional<String> empty = Optional.empty();

3. 错误用法 vs 正确用法#

❌ 错误用法:仅仅当作 null 检查的包装#

很多人刚开始使用 Optional 时,会写出类似下面的代码:

Optional<User> userOpt = findUserById(id);
if (userOpt.isPresent()) {
User user = userOpt.get();
System.out.println(user.getName());
} else {
System.out.println("User not found");
}

这种写法和传统的 if (user != null) 没有任何本质区别,反而增加了包装对象的开销。

✅ 正确用法:使用函数式 API#

Optional 的精髓在于它的链式调用和函数式接口。


4. 核心 API 详解#

4.1 获取值的正确姿势#

永远不要直接调用 get(),除非你 100% 确定值存在。

  • orElse(T other):如果有值则返回,否则返回默认值(无论是否有值,other 都会被计算/创建)。
  • orElseGet(Supplier<? extends T> other):如果有值则返回,否则执行 Supplier 获取默认值(懒加载,只有为空时才执行)。
  • orElseThrow(Supplier<? extends X> exceptionSupplier):如果有值则返回,否则抛出自定义异常。

示例:

User user = Optional.ofNullable(getUser())
.orElse(new User("Default")); // 注意:即使 getUser() 不为空,new User() 也会执行
User userLazy = Optional.ofNullable(getUser())
.orElseGet(() -> new User("Default")); // 推荐:只有为空时才创建对象
User userOrThrow = Optional.ofNullable(getUser())
.orElseThrow(() -> new IllegalArgumentException("User not found"));

4.2 转换值:mapflatMap#

  • map:如果有值,对值执行映射函数。如果映射结果为 null,返回空 Optional
  • flatMap:与 map 类似,但映射函数本身返回的就是一个 Optional,用于避免嵌套的 Optional<Optional<T>>

场景:获取用户的所在城市的名称

// 假设结构:User -> Address -> City -> String name
public String getCityName(User user) {
return Optional.ofNullable(user)
.map(User::getAddress) // 返回 Optional<Address>
.map(Address::getCity) // 返回 Optional<City>
.map(City::getName) // 返回 Optional<String>
.orElse("Unknown");
}

这一行代码优雅地处理了 user 为 null、address 为 null 或 city 为 null 的所有情况。

4.3 过滤值:filter#

如果值存在且满足条件,返回包含该值的 Optional,否则返回空 Optional

Optional<User> user = Optional.ofNullable(getUser())
.filter(u -> u.getAge() > 18); // 只有成年用户会被保留

4.4 执行操作:ifPresent#

如果值存在,执行 Consumer 操作;否则什么都不做。

Optional.ofNullable(getUser())
.ifPresent(u -> System.out.println("User name: " + u.getName()));

5. Java 9+ 的增强#

Java 9 对 Optional 进行了一些实用的增强:

  • ifPresentOrElse(Consumer, Runnable):如果是空值,也能执行操作(类似 if-else)。
Optional.ofNullable(getUser())
.ifPresentOrElse(
u -> System.out.println("Found: " + u),
() -> System.out.println("Not Found")
);
  • or(Supplier):如果为空,返回另一个 Optional
// 尝试从缓存拿,没有则从数据库拿
Optional<User> user = getFromCache(id)
.or(() -> getFromDB(id));
  • stream():将 Optional 转为 Stream。如果存在值,流中包含该值;否则为空流。这在流处理中非常有用。
List<Optional<User>> list = ...;
List<User> users = list.stream()
.flatMap(Optional::stream) // 自动过滤掉空的 Optional 并解包
.collect(Collectors.toList());

6. 使用建议#

  1. 作为返回值Optional 设计的初衷是作为方法的返回值类型,明确告知调用者“这里可能没有值”。
  2. 避免作为字段或参数
  • 不要在类字段中使用 Optional(它没有实现 Serializable,会影响序列化)。
  • 不要Optional 作为方法参数(这会让调用方不得不包装数据,不仅麻烦,还无法解决参数本身为 null 的问题)。
  1. 集合不要用 Optional:不要返回 Optional<List<T>>,直接返回空集合 Collections.emptyList() 更好。
  2. 基本类型专用类:如果是 intlongdouble,尽量使用 OptionalIntOptionalLongOptionalDouble,避免自动装箱/拆箱的性能损耗。

Optional 适合出现在可能没有结果的方法返回值中。字段、参数和集合通常有更直接的表达方式,链式调用也不应替代清晰的条件判断。

Java Optional 使用指南
https://mj3622.github.io/posts/编程实践/java-optional/
作者
Minjer
发布于
2025-11-22
许可协议
CC BY-NC-SA 4.0