Skip to content

Optional

概述

Optional 是 JDK 8 引入的容器类java.util.Optional),用于表示「可能有值,可能没有值」的单一结果。它不替代所有 null,而是为方法返回值提供一种显式表达「无值」的方式,从而减少直接使用 null 导致的空指针异常(NPE),并让调用方必须显式处理「无值」情况。

通俗理解:一个方法返回 Optional<User>,就是在告诉调用方「我可能给你一个 User,也可能没有」;调用方通过 isPresent()orElse()ifPresent() 等方式安全地处理这两种情况,而不是拿到一个可能为 null 的引用再去判空。

前置建议

已掌握 泛型(如 Optional<T>)和 Lambda 表达式。Optional 常与 Stream APIfindFirst()findAny() 等配合使用。


为什么需要 Optional

在 JDK 8 之前,方法若可能「没有结果」,通常返回 null,调用方容易忘记判空而导致 NPE:

java
// 可能返回 null,调用方若忘记判空就会 NPE
public User findUserById(long id) {
    // ...
    return user;  // 或 return null;
}

// 调用处:若 getAddress() 也可能为 null,链式调用易 NPE
String city = findUserById(1).getAddress().getCity();  // 危险!

使用 Optional 后,无值被显式建模,编译器无法帮你强制处理,但 API 设计上会促使调用方用 orElseifPresent 等方式处理空情况:

java
public Optional<User> findUserById(long id) {
    // ...
    return Optional.ofNullable(user);
}

// 调用处:必须考虑「无用户」的情况
Optional<User> opt = findUserById(1);
String city = opt
    .map(User::getAddress)
    .map(Address::getCity)
    .orElse("未知");

创建 Optional

方法说明
Optional.of(value)创建包含非 null 值的 Optional;若传入 null 会立刻抛出 NullPointerException
Optional.ofNullable(value)若 value 为 null 则得到空 Optional,否则包含该值(最常用
Optional.empty()得到空的 Optional 实例
java
Optional<String> a = Optional.of("hello");           // 有值
Optional<String> b = Optional.ofNullable(null);       // 空
Optional<String> c = Optional.empty();               // 空

// Optional.of(null) 会抛出 NPE,不要对可能为 null 的变量使用 of

提示

方法返回值可能为 null 时,用 Optional.ofNullable(result) 包装;确定不为 null 时用 Optional.of(result)。若本来就没有值,直接返回 Optional.empty()


基本用法:判断与取值

判断是否有值

java
Optional<String> opt = Optional.ofNullable(getMessage());

if (opt.isPresent()) {
    System.out.println(opt.get());
}
  • isPresent():有值返回 true,空则 false。
  • get():有值时返回该值;空时抛出 NoSuchElementException,因此应先判空或改用下面更安全的 API。

注意

尽量避免「先 isPresent()get()」的写法,更推荐用 orElseifPresentorElseThrow 等,减少漏判或冗长代码。

版本说明

Optional.isEmpty()(与 isPresent() 相反)自 JDK 11 提供;JDK 8 中请用 !optional.isPresent()

安全取值:orElse、orElseGet、orElseThrow

无值时的默认值或替代行为

java
Optional<String> opt = Optional.ofNullable(getMessage());

// 无值时使用默认值(默认值会先求值)
String s1 = opt.orElse("default");

// 无值时用 Lambda 计算默认值(仅无值时才调用,适合昂贵计算)
String s2 = opt.orElseGet(() -> computeDefault());

// 无值时抛出异常(常用于「必须有值」的业务场景)
String s3 = opt.orElseThrow(() -> new IllegalStateException("缺少必填项"));
方法行为
orElse(T other)无值时返回 otherother 会先求值
orElseGet(Supplier<? extends T>)无值时调用 Supplier 得到结果;无值才调用,适合昂贵计算
orElseThrow(Supplier<? extends X>)无值时抛出异常(JDK 10+ 有无参重载 orElseThrow(),无值抛 NoSuchElementException

条件执行:ifPresent 与 ifPresentOrElse

有值时才执行一段逻辑,无需手写 if (opt.isPresent()) { ... }

java
Optional<String> opt = Optional.ofNullable(getMessage());

// 有值时执行 Lambda,无值什么都不做
opt.ifPresent(s -> System.out.println("消息: " + s));

// JDK 9+:有值执行第一个 Lambda,无值执行第二个
opt.ifPresentOrElse(
    s -> System.out.println("消息: " + s),
    () -> System.out.println("无消息")
);

链式转换:map 与 flatMap

在「有值」的前提下做转换,无值则整条链仍为空 Optional:

java
Optional<User> userOpt = findUserById(1);

// 从 Optional<User> 得到 Optional<String>(例如用户名的长度)
Optional<Integer> lenOpt = userOpt.map(User::getName).map(String::length);

// 若 getUserName() 返回的是 Optional<String>,用 flatMap 避免 Optional<Optional<String>>
Optional<String> nameOpt = userOpt.flatMap(User::getNickName);
  • map(Function):有值时用函数做一次转换,得到 Optional<U>;无值则得到 Optional.empty()
  • flatMap(Function<T, Optional<U>>):有值时用函数得到另一个 Optional,结果被「压平」为 Optional<U>,避免嵌套 Optional<Optional<U>>

示例:组合多个可能为空的步骤:

java
// 获取用户所在城市,任一步无值则结果为 empty
Optional<String> cityOpt = findUserById(1)
    .map(User::getAddress)
    .map(Address::getCity);
String city = cityOpt.orElse("未知");

过滤:filter

有值且满足条件才保留,否则变为空 Optional:

java
Optional<User> opt = findUserById(1);
Optional<User> adult = opt.filter(u -> u.getAge() >= 18);
adult.ifPresent(u -> System.out.println(u.getName()));

完整示例

示例 1:根据 ID 查用户并打印名称

java
public void printUserName(long id) {
    findUserById(id)
        .map(User::getName)
        .ifPresent(name -> System.out.println("用户: " + name));
}

示例 2:无值时使用默认值或抛异常

java
// 配置项:无则用默认值
String timeout = Optional.ofNullable(System.getProperty("app.timeout"))
    .orElse("30");

// 必填项:无则抛业务异常
String id = Optional.ofNullable(request.getParameter("id"))
    .orElseThrow(() -> new IllegalArgumentException("缺少参数 id"));

示例 3:与 Stream 结合(findFirst / findAny)

java
List<User> users = getUsers();
Optional<User> first = users.stream()
    .filter(u -> u.getAge() >= 18)
    .findFirst();
first.ifPresent(u -> System.out.println(u.getName()));

注意事项

不要用 Optional 做成员变量

Optional 的设计意图是方法返回值,而不是字段。将 Optional 作为类的成员变量会增加无谓的包装、序列化与语义歧义,不推荐。字段「可能为 null」时,保持为可空类型,在 getter 中如需可返回 Optional.ofNullable(field)

不要用 Optional 替代所有 null

方法内部实现、局部变量、参数等不必强行改为 Optional;集合「可能为空」应用空集合而非 Optional<List<T>>。Optional 用在可能无单一结果的返回值上最能体现价值。

避免无判空就调用 get()

空 Optional 上调用 get() 会抛出 NoSuchElementException。优先使用 orElseorElseThrowifPresentmap 等 API,避免「先 isPresent 再 get」或直接 get。

提示

链式调用时,任一步得到空 Optional,后续 map/filter 都会跳过,最终得到 empty,用 orElse/orElseThrow 收尾即可,代码简洁且安全。


相关链接

基于 VitePress 构建