1206 words
6 minutes
Java Optional 全面指南:告别空指针,写出优雅代码
2025-11-22

引言:十亿美元的错误#

在 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 转换值: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 不仅仅是一个工具类,更是一种编程思维。它强制开发者在编码阶段就思考“值为空”的场景,并通过链式调用让业务逻辑更加流畅。掌握 Optional,是写出健壮、简洁 Java 代码的必经之路。

Java Optional 全面指南:告别空指针,写出优雅代码
https://mj3622.github.io/posts/编程实践/java-optional/
Author
Minjer
Published at
2025-11-22