JDK JDK 学习手册8 → 27 · JDK 专用学习站
QUICKREF · CORE · OPTIONAL

Optional:把缺失写进类型

JDK 8 · 速查主题 03 / 06 · 预计 22 分钟
核心22 分钟
一句话Optional 用类型表达「可能没有值」,把缺失从隐式 null 变成显式契约。它适合返回值,不适合字段、参数和集合元素;用错会把判空变成拆箱。

学完你要能自己回答

  1. Optional 想解决什么问题?它适合放在哪些位置?
  2. map、flatMap、filter 分别怎么用?为什么 map 可能得到嵌套 Optional?
  3. orElse 和 orElseGet 的求值时机有什么差别?

意图:显式的「可能没有」01

Optional<T> 是一个最多装一个值的容器:Optional.empty() 或 Optional.of(value)。它的价值在方法签名上——调用方看到 Optional,就知道要处理缺失,而不是靠注释或约定。

  • 适合:可能查不到的返回值,例如 findById、findFirst。
  • 不适合:字段、方法参数、集合元素;这些位置用 null、重载或空集合更直接。
  • 不适合:性能敏感的热点路径;每次 map/filter 都会产生新对象。
  • 不适合:序列化边界;Optional 不实现 Serializable,放进 DTO 或 RPC 模型会出问题。
Optional 不是「消除 NPE」的万能药:Optional.of(null) 会立刻 NPE,get() 在空时抛 NoSuchElementException。它只是把「有没有值」变成类型信息。

API 地图:创建、转换、取值02

方法作用空时行为
of / ofNullable / empty创建of(null) 抛 NPE
isPresent / ifPresent判断 / 消费ifPresent 不执行
map / flatMap / filter转换 / 展平 / 过滤返回 empty
orElse / orElseGet / orElseThrow默认值或异常按各自语义
get直接取值抛 NoSuchElementException
static Optional<String> find(String key) {
    Map<String, String> db = new HashMap<>();
    db.put("ada", "Ada Lovelace");
    return Optional.ofNullable(db.get(key));
}

System.out.println(find("ada").map(String::toUpperCase).orElse("none"));
System.out.println(find("alan").map(String::length).orElse(-1));
System.out.println(find("ada").filter(s -> s.startsWith("A")).isPresent());
System.out.println(find("ada").flatMap(s -> Optional.of(s.split(" ")[0])).get());
ADA LOVELACE
-1
true
Ada

map 会自动把返回值包成 Optional;如果函数本身返回 Optional,用 flatMap 避免得到 Optional<Optional<T>>。filter 在条件不满足时直接变 empty。

get() 只应在已经确认有值时使用;更推荐 orElse、orElseGet、orElseThrow 或 ifPresent,让缺失处理留在链式调用里。

orElse vs orElseGet:求值时机03

orElse(T other) 的参数在调用前就求值,不管 Optional 是否为空;orElseGet(Supplier) 只在为空时调用 Supplier。默认值只是一个常量时差别不大;默认值需要查库、创建对象或记录日志时,orElse 会带来无谓副作用。

static String expensive() {
    System.out.println("expensive()");
    return "fallback";
}

System.out.println(Optional.empty().orElse(expensive()));
System.out.println(Optional.of("value").orElse(expensive()));
System.out.println(Optional.of("value").orElseGet(() -> expensive()));
expensive()
fallback
expensive()
value
value

从输出可以看到:Optional 明明有值,orElse(expensive()) 仍然执行了 expensive();orElseGet 没有执行。默认值有副作用时,这个差别就是 bug。

JDK 8 的 orElseThrow 需要传 Supplier;无参 orElseThrow() 是 JDK 10 才加入的。写 8 的代码不要漏参数。

反模式:不要把 Optional 当万能判空04

  • if (opt.isPresent()) opt.get():直接用 map、ifPresent 或 orElse,不要手动拆箱。
  • 把 Optional 当字段或参数:序列化、RPC、ORM 都可能不认,调用方被迫拆箱。
  • 返回 Optional<List<T>>:空列表已经能表达「没有元素」,用 Collections.emptyList()。
  • 在集合里放 Optional:用空集合或先过滤掉缺失元素。
  • 用 Optional 包装基本类型:用 OptionalInt/OptionalLong/OptionalDouble,避免装箱。
try {
    Optional.of(null);
} catch (NullPointerException e) {
    System.out.println(e.getClass().getSimpleName());
}
NullPointerException

Optional.of 要求值非空,Optional.ofNullable 才接受 null。这个命名很容易记反;创建前先问自己「这个值允许为 null 吗」。

JDK 8 的 Optional 没有 isEmpty()、or()、stream()、ifPresentOrElse();这些分别是 11、9、9、9 才补的。不要从新版本教程里直接抄。

与 Stream 配合:展平缺失值05

Stream<Optional<T>> 很常见,例如批量查询后每个 id 得到一个 Optional。JDK 8 没有 Optional.stream(),需要先 filter(Optional::isPresent) 再 map(Optional::get)。

List<Optional<String>> list = Arrays.asList(
        Optional.of("a"), Optional.empty(), Optional.of("b"));

List<String> values = list.stream()
        .filter(Optional::isPresent)
        .map(Optional::get)
        .collect(Collectors.toList());
System.out.println(values);
[a, b]

如果你发现自己频繁写 filter(isPresent).map(get),说明上游可能不该返回 Optional,或者应该提供一个批量查询接口,直接返回找到的结果集合。

Optional 只能保证容器本身不装 null;它包装的对象内部字段仍可能为 null。不要把 Optional 当成深层判空工具。

版本边界BOUNDARY

这张表比 API 本身更值得记。写代码时先确认你的运行时版本。

特性 / API状态说明
`Optional`8 新增随 Stream 一起进入标准库;不是独立的 JEP。
`Optional.isEmpty`11 起JDK 8 用 !isPresent()。
`Optional.or`9 起JDK 8 用 orElseGet 链式替代。
`Optional.stream`9 起JDK 8 用 filter(isPresent).map(get)。
`ifPresentOrElse`9 起JDK 8 需要 if/else 或分开处理。
无参 `orElseThrow()`10 起JDK 8 必须传 Supplier。
`Optional` 序列化不实现 Serializable不要放进 DTO、RPC 参数或持久化模型。

费曼自检SELF CHECK

先合上页面,用自己的话回答,再展开对照。能讲出因果和反例,才算真正掌握。

01. Optional 最适合放在哪里?为什么不适合字段和参数?
最适合可能缺失的返回值。字段和参数会强迫调用方拆箱,增加序列化、RPC、ORM 的兼容问题,也没有比 null 或重载更安全;集合元素则应该用空集合表达。
02. map 和 flatMap 在 Optional 里怎么选?
函数返回普通值时用 map,它会自动包一层 Optional;函数本身返回 Optional 时用 flatMap,避免得到 Optional<Optional<T>>。
03. 为什么 orElse(expensive()) 可能比 orElseGet 多执行一次?
orElse 的参数是普通表达式,在调用方法前就求值,不管 Optional 是否为空;orElseGet 接收 Supplier,只在为空时调用。默认值昂贵或有副作用时必须用 orElseGet。
04. Optional 能彻底消除 NPE 吗?
不能。Optional.of(null) 会立刻 NPE,get() 在空时抛 NoSuchElementException,从 Optional 里取出的对象仍可能包含 null 字段。它只能让「值可能缺失」在类型上更显式。
学习进度 记录保存在本机浏览器 · 不上传

JDK 学习手册 · JDK / Java 专用学习站 · 纯静态 · 双击 index.html 即可使用

本站专注 JDK 版本演进与 Java 教学,非 Oracle / OpenJDK 官方站点。版本数据来自 OpenJDK 官方 Features 表;课程示例已在对应本机 JDK 实测。构建日期 2026-11-12。