引言:十亿美元的错误
在 Java 开发中,NullPointerException (NPE) 无疑是最令人头疼的异常之一。Tony Hoare 曾将空引用称为“十亿美元的错误”。为了解决这个问题,Java 8 引入了 java.util.Optional 类。
Optional 不是为了完全消除 null,而是为了清晰地表达“值可能不存在”这一语义,并提供一套函数式 API 来优雅地处理空值,从而减少防御性代码(if (obj != 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 转换值:map 与 flatMap
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. 最佳实践总结
- 作为返回值:
Optional设计的初衷是作为方法的返回值类型,明确告知调用者“这里可能没有值”。 - 避免作为字段或参数:
- 不要在类字段中使用
Optional(它没有实现Serializable,会影响序列化)。 - 不要将
Optional作为方法参数(这会让调用方不得不包装数据,不仅麻烦,还无法解决参数本身为null的问题)。
- 集合不要用 Optional:不要返回
Optional<List<T>>,直接返回空集合Collections.emptyList()更好。 - 基本类型专用类:如果是
int、long、double,尽量使用OptionalInt、OptionalLong、OptionalDouble,避免自动装箱/拆箱的性能损耗。
结语
Optional 不仅仅是一个工具类,更是一种编程思维。它强制开发者在编码阶段就思考“值为空”的场景,并通过链式调用让业务逻辑更加流畅。掌握 Optional,是写出健壮、简洁 Java 代码的必经之路。

