← 返回题目列表

Optional 应该如何使用?orElse 和 orElseGet 有什么区别?

高频 简单 第 2 / 24 题 更新于 2026/07/26
Optional空值处理orElseGetmap

简化版

Optional<T> 用于明确表达“返回结果可能不存在”,重点是让调用方显式处理空值,而不是消灭所有 nullorElse 总会先计算备用值,orElseGet 只在 Optional 为空时调用 Supplier,所以备用值昂贵或有副作用时应使用 orElseGet

详细版

Optional.of(value) 要求 value 非空,ofNullable(value) 同时接收非空值和 nullempty() 表示无值。取值时优先使用 mapflatMapfilterorElseGetorElseThrow 等组合操作,不要把 isPresent() + get() 写成另一种空值判断。

map 的映射函数返回普通值,若结果为 null 会变为空 Optional;flatMap 用于映射函数本身已经返回 Optional 的场景,避免得到嵌套结构。Optional 主要适合作为方法返回类型,通常不应到处用作实体字段、DTO 字段或方法参数,而且 Optional 引用自身也不应该是 null

完整版教学

一、Optional 解决什么问题

返回 null 时,方法签名无法提醒调用方结果可能缺失。返回 Optional<User> 则把“可能没有用户”写入类型,调用方必须选择默认值、抛异常、继续转换或忽略结果。

Optional<User> findUser(long id) {
    return Optional.ofNullable(repository.findById(id));
}

它适合表达单个返回值的缺失,不代表所有引用都应包装。集合查询没有结果时通常返回空集合;方法参数是否允许为空,使用清晰的 API 拆分或校验往往比接收 Optional 更直接。

二、orElse 为什么可能白做工作

Java 会先求出普通方法参数,再调用方法,因此 orElse 的参数无论是否需要都会执行:

User user1 = cached.orElse(loadFromDatabase());
User user2 = cached.orElseGet(this::loadFromDatabase);

即使 cached 有值,第一行仍会访问数据库;第二行只有在为空时才调用 Supplier。如果备用值只是常量或已经存在的对象,orElse 更简洁;如果创建成本高或存在副作用,应使用 orElseGet

三、用组合代替手动拆箱

String city = findUser(id)
        .map(User::getAddress)
        .map(Address::getCity)
        .filter(name -> !name.isBlank())
        .orElse("未知城市");

这段代码在任一步没有结果时得到空 Optional,最后统一提供默认值。相比反复判断 null 或先 isPresent()get(),数据流和缺失策略更集中。

User::findAddress 返回 Optional<Address>,应写 flatMap(User::findAddress);使用 map 会得到 Optional<Optional<Address>>

四、常见误用

  • Optional 变量自身赋为 null:同时引入两套缺失语义,完全违背设计目的。
  • 无条件调用 get():空值时抛 NoSuchElementException,且没有表达业务含义;应改用组合方法或带异常 Supplier 的 orElseThrow
  • 给每个字段套 Optional:会影响序列化、框架映射和对象模型可读性,也没有自动获得更好的空值设计。
  • 把 Optional 当容器集合:它最多表达一个值,批量结果应使用集合或 Stream。
  • 在 Optional 对象上同步:Optional 是 value-based class,不应依赖对象身份或拿它作为锁。

五、异常策略要表达业务语义

当缺失属于正常分支,可以返回默认值或继续保持 Optional;当缺失违反业务前置条件,应在边界处抛出有意义的异常:

User user = findUser(id)
        .orElseThrow(() -> new UserNotFoundException(id));

这比 get() 抛出的通用异常更利于定位问题。Optional 只是建模工具,真正重要的是调用方明确选择“缺失时怎么办”。

六、用调用次数看 orElse 与 orElseGet

假设缓存命中率为 99%,处理 100,000 次请求。若写 cached.orElse(load()),昂贵的 load() 仍会执行约 100,000 次;改为 orElseGet(this::load) 后,理想情况下只在约 1,000 次未命中时执行。二者返回结果可能相同,但成本和副作用完全不同。

orElse:先计算备用值 → 再调用 Optional 方法
orElseGet:Optional 为空 → 才调用 Supplier
方法有值时空值时适用场景
orElse(value)仍已计算 value返回 value常量或廉价对象
orElseGet(supplier)不调用 supplier延迟生成昂贵/有副作用备用值
orElseThrow返回原值抛业务异常缺失违反前置条件
ifPresentOrElse执行 Consumer执行空分支两侧都是动作

七、API 边界与框架兼容

Optional 是 value-based class,不应依赖实例身份、用 == 判断空 Optional 或把它作为同步锁。它也不是字段必然不可序列化的统一结论,而是多数实体、DTO 和框架契约并不把 Optional 当作自然字段模型;是否支持要查具体框架,不能用流行规则代替文档。

Optional.map 的 mapper 若返回 null,会得到 empty;flatMap 的 mapper 必须返回非 null Optional,否则会抛 NullPointerException。这个差异说明 Optional 仍要求函数遵守契约,不会自动吞掉所有错误。

心法:Optional 负责把“可能缺失”写进返回类型,真正的设计仍是选择缺失时继续、默认、忽略还是失败。

八、常见误区与追问

  • 误区:Optional 可以消灭项目中的所有 null。 它主要表达返回值缺失,框架边界和既有 API 仍可能出现 null。
  • 误区:orElse 只在为空时计算参数。 Java 先求普通实参,因此有值时昂贵方法也已执行。
  • 误区:isPresent() + get() 就是最佳实践。 这通常只是换皮 null 判断,组合操作或业务化 orElseThrow 更清晰。
  • 误区:Optional 适合所有实体字段和方法参数。 这会增加对象模型与框架映射复杂度,需基于具体 API 契约决定。
  • 追问:map 返回 null 会怎样? 结果变成 empty;若映射本身返回 Optional,应使用 flatMap 避免嵌套。
  • 追问:为什么不能锁住 Optional? 它是 value-based class,调用者不应依赖对象身份,未来实现也不保证同步行为可靠。
  • 追问:集合查询无结果为什么通常返回空集合? 批量结果天然用集合表达零个元素,不需要再套 Optional 形成两层缺失语义。

九、加强记忆

Optional 最适合放在返回类型上表达“可能无值”,自身绝不能再设为 null。用 mapflatMap 继续计算,用 orElseThrow 表达失败;orElse 会提前计算参数,orElseGet 才是按需生成备用值。