148

I've been working with the new Optional type in Java 8, and I've come across what seems like a common operation that isn't supported functionally: an "orElseOptional"

Consider the following pattern:

Optional<Result> resultFromServiceA = serviceA(args);
if (resultFromServiceA.isPresent) return result;
else {
    Optional<Result> resultFromServiceB = serviceB(args);
    if (resultFromServiceB.isPresent) return resultFromServiceB;
    else return serviceC(args);
}

There are many forms of this pattern, but it boils down to wanting an "orElse" on an optional that takes a function producing a new optional, called only if the current one does not exist.

It's implementation would look like this:

public Optional<T> orElse(Supplier<Optional<? extends T>> otherSupplier) {
    return value != null ? this : other.get();
}

I'm curious if there's a reason such a method doesn't exist, if I'm just using Optional in an unintended way, and what other ways people have come up with to deal with this case.

I should say that I think that solutions involving custom utility classes/methods aren't elegant because people working with my code won't necessarily know they exist.

Also, if anyone knows, will such a method be included in JDK 9, and where might I propose such a method? This seems like a pretty glaring omission to the API to me.

4

6 回答 6

99

这是 JDK 9 的一部分or,它采用Supplier<Optional<T>>. 您的示例将是:

return serviceA(args)
    .or(() -> serviceB(args))
    .or(() -> serviceC(args));

有关详细信息,请参阅Javadoc或我写的这篇文章。

于 2016-07-25T05:56:50.527 回答
67

给定当前 API,最简洁的“尝试服务”方法是:

Optional<Result> o = Stream.<Supplier<Optional<Result>>>of(
    ()->serviceA(args), 
    ()->serviceB(args), 
    ()->serviceC(args), 
    ()->serviceD(args))
.map(Supplier::get)
.filter(Optional::isPresent)
.map(Optional::get)
.findFirst();

重要的方面不是您必须编写一次的(恒定的)操作链,而是添加另一个服务(或修改一般的服务列表)是多么容易。在这里,添加或删除一个()->serviceX(args)就足够了。

由于流的延迟评估,如果前面的服务返回非空,则不会调用任何服务Optional


从 Java 9 开始,您可以将代码简化为

Optional<Result> o = Stream.<Supplier<Optional<Result>>>of(
    ()->serviceA(args), 
    ()->serviceB(args), 
    ()->serviceC(args), 
    ()->serviceD(args))
.flatMap(s -> s.get().stream())
.findFirst();

虽然这个答案已经包含了一个更简单的 JDK 9 方法。

JDK 16 提供了替代方案

Optional<Result> o = Stream.<Supplier<Optional<Result>>>of(
    ()->serviceA(args), 
    ()->serviceB(args), 
    ()->serviceC(args), 
    ()->serviceD(args))
.<Result>mapMulti((s,c) -> s.get().ifPresent(c))
.findFirst();

Consumer尽管这种方法对于接受 a而不是返回 a 的服务方法可能更方便Supplier

于 2015-03-03T13:58:49.523 回答
36

它不漂亮,但这会起作用:

return serviceA(args)
  .map(Optional::of).orElseGet(() -> serviceB(args))
  .map(Optional::of).orElseGet(() -> serviceC(args))
  .map(Optional::of).orElseGet(() -> serviceD(args));

.map(func).orElseGet(sup)是一个相当方便的模式,用于Optional. 它的意思是“如果这Optional包含价值v,给我func(v),否则给我sup.get()”。

在这种情况下,我们调用serviceA(args)并得到一个Optional<Result>. 如果它Optional包含 value v,我们想要 get Optional.of(v),但如果它是空的,我们想要 get serviceB(args)。用更多替代品重复冲洗。

这种模式的其他用途是

  • .map(Stream::of).orElseGet(Stream::empty)
  • .map(Collections::singleton).orElseGet(Collections::emptySet)
于 2015-03-02T23:26:32.230 回答
29

也许这就是你所追求的:从一个可选的或另一个中获取价值

否则,您可能想看看Optional.orElseGet. 这是我认为您所追求的示例:

result = Optional.ofNullable(serviceA().orElseGet(
                                 () -> serviceB().orElseGet(
                                     () -> serviceC().orElse(null))));
于 2015-03-02T20:10:42.167 回答
6

假设您仍在使用 JDK8,则有多种选择。

选项#1:制作自己的辅助方法

例如:

public class Optionals {
    static <T> Optional<T> or(Supplier<Optional<T>>... optionals) {
        return Arrays.stream(optionals)
                .map(Supplier::get)
                .filter(Optional::isPresent)
                .findFirst()
                .orElseGet(Optional::empty);
    }
}

这样你就可以做到:

return Optionals.or(
   ()-> serviceA(args),
   ()-> serviceB(args),
   ()-> serviceC(args),
   ()-> serviceD(args)
);

选项#2:使用库

例如 google guava 的 Optional 支持正确的or()操作(就像 JDK9 一样),例如:

return serviceA(args)
  .or(() -> serviceB(args))
  .or(() -> serviceC(args))
  .or(() -> serviceD(args));

(每个服务返回com.google.common.base.Optional,而不是java.util.Optional)。

于 2018-03-08T23:59:11.960 回答
2

这看起来很适合模式匹配和带有 Some 和 None 实现的更传统的 Option 接口(例如JavaslangFunctionalJava中的那些)或cyclops -react中的惰性Maybe实现。我是这个库的作者。

使用cyclops-react,您还可以在 JDK 类型上使用结构模式匹配。对于 Optional,您可以通过访问者模式匹配当前和不存在的情况。它看起来像这样 -

  import static com.aol.cyclops.Matchables.optional;

  optional(serviceA(args)).visit(some -> some , 
                                 () -> optional(serviceB(args)).visit(some -> some,
                                                                      () -> serviceC(args)));
于 2015-09-18T13:22:14.770 回答