Menu

Java Optional:告别 null 和 NullPointerException

java.util.Optional 是什么,如何创建它,以及如何用 map、filter、orElse 和 ifPresent 安全地读取它的值,而不是到处写 null 检查。

本页包含可运行的编辑器。编辑、运行,立即查看输出。

Optional 要解决的问题

一个可能给不出答案的方法,在 Java 中一直面临一个尴尬的选择:返回 null,然后指望调用者记得检查。调用者通常都会忘,结果就是一个 NullPointerException,而且它出现的地方离返回 null 的那个方法很远。

Optional<T> 是一个小盒子,要么装着一个值,要么明确是空的。方法返回 Optional<User> 而不是 User,就等于在签名里直接告诉调用者“这里可能什么都找不到”,编译器也会引导他们去处理这种情况。

创建 Optional

有三个工厂方法,选对很重要:

  • Optional.of(value):值必须不为 null,否则当场抛出 NullPointerException。
  • Optional.ofNullable(value):值为 null 时是空的,否则就装着这个值。用它来包装可能为 null 的东西。
  • Optional.empty():一个空的 Optional。

一个常见错误是对可能为 null 的值使用 Optional.of(x):这样做违背了初衷,抛出的恰恰是你想避免的那个异常。拿不准的时候,就用 ofNullable。

安全地读取值

拿到一个 Optional 之后,目标是在不假定值存在的前提下把它取出来。最粗暴的工具是 get(),你几乎永远都不应该用它:

Optional<String> name = Optional.empty();
String value = name.get();   // throws NoSuchElementException - just NPE in disguise

正确的做法是提供一个备选值,或者在值存在时做出响应。orElse 在为空时返回一个默认值;ifPresent 只在值存在时运行代码:

当默认值的构建代价很高时,用 orElseGet(supplier) 代替 orElse:只有 Optional 真的为空时,supplier 才会运行。而 orElse 的参数总是会被计算,即使根本用不上。

用 orElseThrow 表达“这个值必须存在”

有时候为空确实是一种错误,比如必需的配置项、本应存在于数据库中的用户。orElseThrow 会把一个空的 Optional 变成一个由你指定的、清晰的异常:

这比写一个 if (opt.isPresent()) 代码块好读得多,而且让失败在发生的地方就清清楚楚地暴露出来。

用 map 和 filter 转换

真正的好处在于链式调用。map 只在值存在时对它应用一个函数,空的 Optional 依然保持为空,所以你在转换时永远不会碰到 null。filter 在值不满足条件时把它丢掉。

如果你的映射函数本身就返回一个 Optional,就用 flatMap 代替 map,以免得到别扭的 Optional<Optional<T>>。这和你在 Stream 中看到的 map/flatMap 的区别一样:Optional 的行为很像一个只有零个或一个元素的流。

Optional 该用在哪里(以及不该用在哪里)

Optional 是为可能合理地没有结果的方法设计的返回类型,典型的例子有 Stream.findFirst()、类似 Map 的查找,以及解析。在这些地方使用它,你的 API 就能自己说明哪里可能没有值。

它不适合用于:

  • 字段。 它不可序列化,还会给每个字段多加一个对象。请使用普通引用,并在构造方法中校验。
  • 方法参数。 调用者不得不把实参包进 Optional.of(...),这比重载或者一个可为 null 的参数更啰嗦。
  • 集合。 返回一个空的 List,而不是 Optional<List>。空集合本身就表示“什么都没有”。

把 Optional 当作返回类型来用,它就能把悄无声息的 null bug 变成编译时的提醒,促使你处理值缺失的情况。

下一页:异常

Optional 能干净地处理日常的“可能没有值”的情况,但有些失败确实是异常情况:打不开的文件、断掉的网络、无法解析的输入。Java 用异常来表示这些情况,这就是下一页的内容。

常见问题

Java 中的 Optional 是什么,为什么要用它?

Optional<T> 是一个容器,要么装着一个值,要么是空的。它的作用是在方法的返回类型中明确表达“可能没有值”,促使调用者去处理空的情况,而不是忘了做 null 检查、在运行时撞上 NullPointerException。它主要用作可能没有结果的方法的返回类型,比如一次什么都没找到的查找。

Optional.of 和 Optional.ofNullable 有什么区别?

如果 x 是 null,Optional.of(x) 会立即抛出 NullPointerException,所以在确定值不为 null 时使用它。Optional.ofNullable(x) 在 x 为 null 时返回一个空的 Optional,否则返回一个有值的 Optional,所以在值可能为 null 时使用它。Optional.empty() 则直接给你一个空的 Optional。

应该调用 Optional.get() 来读取值吗?

避免直接使用 get():如果 Optional 是空的,它会抛出 NoSuchElementException,这不过是换了个名字的 NullPointerException。优先使用 orElse、orElseGet、orElseThrow、ifPresent 或 map,这样空的情况总能得到处理。如果非要先检查,可以用 isPresent() 做保护,但函数式的方法更简洁。

Coddy 编程语言插图

和 Coddy 一起学编程

开始