组合可选值最优雅的方法是什么?

What's the most elegant way to combine optionals?

这是我目前得到的:

Optional<Foo> firstChoice = firstChoice();
Optional<Foo> secondChoice = secondChoice();
return Optional.ofNullable(firstChoice.orElse(secondChoice.orElse(null)));

这让我觉得既丑陋又浪费。如果存在 firstChoice,我将不必要地计算 secondChoice。

还有一个更高效的版本:

Optional<Foo> firstChoice = firstChoice();
if(firstChoice.isPresent()) {
 return firstChoice;
} else {
 return secondChoice();
}

这里我不能在不复制映射器或声明另一个局部变量的情况下将某些映射函数链接到最后。所有这些使得代码比实际解决的问题更复杂。

我更愿意这样写:

return firstChoice().alternatively(secondChoice());

然而Optional::alternatively显然不存在。现在怎么办?

您可以简单地将其替换为,

Optional<Foo> firstChoice = firstChoice();
return firstChoice.isPresent()? firstChoice : secondChoice();

除非 firstChoice.isPresent() 为 false,否则不会调用上述代码。

但是您必须准备调用这两个函数以获得所需的输出。没有其他方法可以逃避检查。

  • 最好的情况是首选返回 true。
  • 最坏的情况是首选返回 false,因此采用另一种方法 征求第二选择。

试试这个:

firstChoice().map(Optional::of)
             .orElseGet(this::secondChoice);

地图方法给你一个Optional<Optional<Foo>>。然后,orElseGet 方法将其扁平化为 Optional<Foo>secondChoice 方法只有在 firstChoice() returns 空可选时才会被评估。

也许是这样的:

Optional<String> finalChoice = Optional.ofNullable(firstChoice()
    .orElseGet(() -> secondChoice()
    .orElseGet(() -> null)));

发件人:

这是一种适用于流中任意数量的 Optional 的方法 API:

return Arrays.asList(firstChoice, secondChoice).stream()
  .filter(Optional::isPresent)
  .map(Optional::get)
  .findFirst().orElse(null);

这不是最短的。但更通俗易懂。

如果您已经在使用其中一个库,另一种方法是使用来自 commons-lang 的 Guava 的 firstNonNull()

firstNonNull(firstChoice.orElse(null), secondChoice.orElse(null));

这是@marstran 对任意数量的可选方案的概括:

@SafeVarargs
public static <T> Optional<T> selectOptional(Supplier<Optional<T>>... optionals) {
    return Arrays.stream(optionals)
            .reduce((s1, s2) -> () -> s1.get().map(Optional::of).orElseGet(s2))
            .orElse(Optional::empty).get();
}

测试:

public static Optional<String> first() {
    System.out.println("foo called");
    return Optional.empty();
}

public static Optional<String> second() {
    System.out.println("bar called");
    return Optional.of("bar");
}

public static Optional<String> third() {
    System.out.println("baz called");
    return Optional.of("baz");
}

public static void main(String[] args) {
    System.out.println(selectOptional(() -> first(), () -> second(), () -> third()));
}

输出:

foo called
bar called
Optional[bar]

java 8 不支持这一点让我很沮丧,所以我切换回了 guava 的可选选项,其中包含 or:

public abstract Optional<T> or(Optional<? extends T> secondChoice)

Returns this Optional if it has a value present; secondChoice otherwise.

惰性计算和任意数量的 Optional 元素

Stream.<Supplier<Optional<Foo>>>of(
        this::firstChoice,
        this::secondChoice
).map(
        Supplier::get
).filter(
        Optional::isPresent
).findFirst(
).orElseGet(
    Optional::empty
);

Java 9 为这种情况添加了Optional.or​(Supplier<? extends Optional<? extends T>> supplier)方法。

给定方法 firstChoice()secondChoice(),每个 return Optional<Foo>,下面的单行代码使用 Optional.or 来达到预期的结果:

return firstChoice().or(this::secondChoice);

这种方法的额外好处是当 firstChoice 为空时仅计算 secondChoice