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 实现懒加载
我们可以通过 map
和 orElseGet
组合来实现真正的懒加载:
@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