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(),除非你的默认值是轻量级且已存在的对象。

一个简单的选择,可能影响系统吞吐量。别让“看起来差不多”的代码,成为你服务的隐形瓶颈。

完整示例代码见:https://github.com/baeldung/java-tutorials


原始标题:Java Optional - orElse() vs orElseGet()