1. 概述
Java 的 Optional
类提供了多种避免空指针的手段,其中 orElse()
和 orElseGet()
是两个看似相似但行为差异显著的方法。
很多开发者在初期容易混淆二者,甚至在生产环境中“踩坑”。本文将直击本质,用实际例子讲清楚它们的区别,并告诉你什么时候该用哪个。
2. 方法签名对比
先看两个方法的定义:
public T orElse(T other)
public T orElseGet(Supplier<? extends T> other)
✅ orElse(T other)
:接收一个 实际的对象实例,无论 Optional 是否为空,这个对象(或表达式)都会被提前计算。
✅ orElseGet(Supplier<T>)
:接收一个 Supplier
函数式接口,只有当 Optional 为空时,才会触发 get()
调用去生成默认值。
根据官方 Javadoc:
orElse()
:值存在就返回,否则返回传入的other
orElseGet()
:值存在就返回,否则执行Supplier
并返回其结果
关键区别就一句话:
⚠️ orElse()
的参数总是会被执行;orElseGet()
的 Supplier
只有在 null 时才执行。
3. 实际行为差异
3.1 orElse():不管有没有值,都执行默认逻辑
假设我们有如下方法,用于生成随机用户名:
private static final List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David", "Eve");
public String getRandomName() {
System.out.println("getRandomName() method - start");
Random random = new Random();
int index = random.nextInt(5);
System.out.println("getRandomName() method - end");
return names.get(index);
}
现在我们这样调用:
String name = Optional.of("baeldung")
.orElse(getRandomName());
虽然 Optional
已经明确包含了 "baeldung"
,但控制台依然输出:
getRandomName() method - start
getRandomName() method - end
❌ 踩坑点:getRandomName()
被执行了!即使根本用不上这个结果。
原因很简单:Java 在调用 orElse()
前,必须先求出 getRandomName()
的返回值,作为参数传进去 —— 这是方法调用的基本机制。
3.2 orElseGet():懒加载式提供默认值
改用 orElseGet()
:
String name = Optional.of("baeldung")
.orElseGet(() -> getRandomName());
这次控制台什么都没输出。
✅ 正确行为:因为 Optional
中已有值,Supplier
根本不会被调用。
只有当你写成:
String name = Optional.<String>empty()
.orElseGet(() -> getRandomName());
才会触发 getRandomName()
的执行。
这说明:Supplier
是延迟执行的,真正做到了“只在需要时才计算”。
4. 性能对比:用 JMH 数据说话
为了量化差异,我们使用 JMH(Java Microbenchmark Harness)做基准测试。
@Benchmark
@BenchmarkMode(Mode.AverageTime)
public String orElseBenchmark() {
return Optional.of("baeldung").orElse(getRandomName());
}
@Benchmark
@BenchmarkMode(Mode.AverageTime)
public String orElseGetBenchmark() {
return Optional.of("baeldung").orElseGet(() -> getRandomName());
}
运行结果(单位:纳秒/操作):
Benchmark Mode Cnt Score Error Units
orElseBenchmark avgt 20 60934.425 ± 15115.599 ns/op
orElseGetBenchmark avgt 20 3.798 ± 0.030 ns/op
✅ 结论非常明显:
orElse()
平均耗时约 60 微秒,因为它每次都调用getRandomName()
orElseGet()
仅 3.8 纳秒,几乎只是取值开销
⚠️ 即使是简单的方法调用,性能差距也能达到上万倍。如果默认值生成涉及数据库查询、远程调用或复杂计算,后果更严重。
5. 使用建议与注意事项
除了性能,还有几个关键点你必须知道:
✅ 推荐使用 orElseGet() 的场景
- 默认值需要通过方法调用获取(如
generateDefault()
) - 构造成本高(new 对象、IO、网络、加密等)
- 带有副作用的操作(如记录日志、更新状态、插入数据库)
✅ 可以使用 orElse() 的场景
- 默认值是常量或已存在的对象:
String name = Optional.ofNullable(userName).orElse("unknown");
- 对象已经创建好,直接引用即可:
User defaultUser = new User("default"); User user = Optional.ofNullable(dbUser).orElse(defaultUser);
❌ 绝对要避免的写法
// 错误示范:每次都会执行 new User(),哪怕 Optional 不为空
User user = Optional.ofNullable(dbUser).orElse(new User("default"));
// 正确写法:延迟初始化
User user = Optional.ofNullable(dbUser).orElseGet(() -> new User("default"));
6. 总结
对比项 | orElse() | orElseGet() |
---|---|---|
参数类型 | T |
Supplier<T> |
执行时机 | 总是执行参数表达式 | 仅在为空时执行 Supplier |
性能 | 差(提前计算) | 好(懒加载) |
适用场景 | 默认值是常量或已构建对象 | 默认值需动态生成 |
📌 最佳实践建议:
默认优先使用
orElseGet()
,除非你的默认值是轻量级且已存在的对象。
一个简单的选择,可能影响系统吞吐量。别让“看起来差不多”的代码,成为你服务的隐形瓶颈。