Optional 应该如何使用?orElse 和 orElseGet 有什么区别?
简化版
Optional<T> 用于明确表达“返回结果可能不存在”,重点是让调用方显式处理空值,而不是消灭所有 null。orElse 总会先计算备用值,orElseGet 只在 Optional 为空时调用 Supplier,所以备用值昂贵或有副作用时应使用 orElseGet。
详细版
Optional.of(value) 要求 value 非空,ofNullable(value) 同时接收非空值和 null,empty() 表示无值。取值时优先使用 map、flatMap、filter、orElseGet、orElseThrow 等组合操作,不要把 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。用 map 和 flatMap 继续计算,用 orElseThrow 表达失败;orElse 会提前计算参数,orElseGet 才是按需生成备用值。