1. 简介

在实际开发中,我们经常会遇到这样的场景:当一个 Optional 为空时,希望“ fallback”到另一个 Optional 实例,而不是直接返回一个原始值。

这听起来很简单,但实现起来却比想象中复杂一些。本文就来深入探讨如何优雅地实现这种“Optional 套 Optional”的 fallback 逻辑。

如果你对 Java 的 Optional 类还不熟悉,建议先阅读我们之前的 Java Optional 入门指南

2. Java 8 的解决方案

Java 8 的 Optional 类并没有提供直接返回另一个 Optional 的方法。 换句话说,orElse()orElseGet() 都只能返回目标类型的值,而不是 Optional<T> 本身。

因此,我们只能自己动手,丰衣足食:

public static <T> Optional<T> or(Optional<T> optional, Optional<T> fallback) {
    return optional.isPresent() ? optional : fallback;
}

这个自定义的 or() 方法非常直观:如果第一个 Optional 有值,就返回它;否则返回备用的 Optional

来看两个测试用例,验证其行为:

当第一个 Optional 有值时,返回第一个

@Test
public void givenOptional_whenValue_thenOptionalGeneralMethod() {
    String name = "Filan Fisteku";
    String missingOptional = "Name not provided";
    Optional<String> optionalString = Optional.ofNullable(name);
    Optional<String> fallbackOptionalString = Optional.ofNullable(missingOptional);
 
    assertEquals(
      optionalString, 
      Optionals.or(optionalString, fallbackOptionalString));
}

当第一个 Optional 为空时,返回 fallback

@Test
public void givenEmptyOptional_whenValue_thenOptionalGeneralMethod() {
    Optional<String> optionalString = Optional.empty();
    Optional<String> fallbackOptionalString = Optional.ofNullable("Name not provided");
 
    assertEquals(
      fallbackOptionalString, 
      Optionals.or(optionalString, fallbackOptionalString));
}

2.1 警惕:缺少懒加载(Lazy Evaluation)

上面的方案虽然能用,但有个致命的性能问题⚠️:调用 or() 方法时,两个 Optional 参数都会被提前求值

想象一下,这两个 Optional 来自两个耗时的数据库查询。如果第一个已经有值了,我们根本不需要执行第二个查询,但上面的代码还是会执行,这就造成了资源浪费。

举个例子:

public class ItemsProvider {
    public Optional<String> getNail(){
        System.out.println("Returning a nail");
        return Optional.of("nail");
    }

    public Optional<String> getHammer(){
        System.out.println("Returning a hammer");
        return Optional.of("hammer");
    }
}

如果我们用自定义的 or() 方法:

// ❌ 错误示范:两个方法都会被执行
Optional<String> item = or(itemsProvider.getNail(), itemsProvider.getHammer());

这会导致“Returning a nail”和“Returning a hammer”都被打印。

正确姿势:利用 orElseGet 实现懒加载

我们可以通过 maporElseGet 组合来实现真正的懒加载:

@Test
public void givenTwoOptionalMethods_whenFirstNonEmpty_thenSecondNotEvaluated() {
    ItemsProvider itemsProvider = new ItemsProvider();

    Optional<String> item = itemsProvider.getNail()
            .map(Optional::of)           // 把值包装成 Optional
            .orElseGet(itemsProvider::getHammer); // 只有第一个为空时才执行

    assertEquals(Optional.of("nail"), item);
}

这个测试只会输出 Returning a nail,完美避开了不必要的调用。这个技巧堪称简单粗暴有效,建议集合。

3. Java 9 的 or() 方法

Java 9 终于原生支持了这个功能! 它为 Optional 类添加了一个 or(Function<? super T, ? extends Optional<? extends T>> supplier) 方法。

这个方法接受一个 Supplier 函数,当原 Optional 为空时,才会执行该函数来获取 fallback 的 Optional,完美解决了懒加载问题。

来看例子:

public static Optional<String> getName(Optional<String> name) {
    return name.or(() -> getCustomMessage());
}

辅助方法:

private static Optional<String> getCustomMessage() {
    return Optional.of("Name not provided");
}

测试一下:

有值时,直接返回原 Optional

@Test
public void givenOptional_whenValue_thenOptional() {
    String name = "Filan Fisteku";
    Optional<String> optionalString = Optional.ofNullable(name);
    assertEquals(optionalString, Optionals.getName(optionalString));
}

空值时,返回 fallback Optional

@Test
public void givenEmptyOptional_whenInvoke_thenFallbackOptional() {
    Optional<String> emptyOptional = Optional.empty();
    Optional<String> result = Optionals.getName(emptyOptional);
    assertEquals(Optional.of("Name not provided"), result);
}

可以看到,Java 9 的 or() 方法不仅 API 更清晰,而且天然支持懒加载,是目前最推荐的方案。

4. 使用 Guava 的 Optional

如果你还在用 Java 8,又不想自己造轮子,可以考虑 Google Guava 库。

Guava 早就提供了类似的功能。首先引入依赖:

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>31.0.1-jre</version>
</dependency>

Guava 的 Optional 也有一个 or() 方法,用法几乎和 Java 9 一模一样:

public static com.google.common.base.Optional<String> 
  getOptionalGuavaName(com.google.common.base.Optional<String> name) {
    return name.or(getCustomMessageGuava());
}
private static com.google.common.base.Optional<String> getCustomMessageGuava() {
    return com.google.common.base.Optional.of("Name not provided");
}

测试用例:

@Test
public void givenGuavaOptional_whenInvoke_thenOptional() {
    String name = "Filan Fisteku";
    com.google.common.base.Optional<String> stringOptional = 
        com.google.common.base.Optional.of(name);
 
    assertEquals(name, Optionals.getOptionalGuavaName(stringOptional).get());
}
@Test
public void givenGuavaOptional_whenNull_thenDefaultText() {
    assertEquals(
      com.google.common.base.Optional.of("Name not provided"), 
      Optionals.getOptionalGuavaName(com.google.common.base.Optional.fromNullable(null)));
}

⚠️ 注意:虽然 Guava 的 Optional 功能强大,但在新项目中还是建议优先使用 JDK 自带的 Optional,避免引入不必要的依赖。

5. 总结

方案 版本 是否懒加载 推荐度
自定义 or() Java 8 ❌ 否 ⭐⭐
map().orElseGet() 技巧 Java 8 ✅ 是 ⭐⭐⭐⭐
Java 9+ or() Java 9+ ✅ 是 ⭐⭐⭐⭐⭐
Guava Optional.or() Any ✅ 是 ⭐⭐⭐

核心结论:

  • Java 9+ 用户:直接用 Optional.or(),最简单最安全。
  • Java 8 用户:优先使用 map().orElseGet() 的组合技巧来实现懒加载。
  • ⚠️ 避免在 Java 8 中提前计算两个 Optional,小心性能“踩坑”。

本文所有代码示例均可在 GitHub 上找到:https://github.com/eugenp/tutorials/tree/master/core-java-modules/core-java-optional


原始标题:Optional orElse Optional in Java