使用 Java 8 的 Optional with Stream::flatMap

2022-08-31 05:16:38

新的Java 8流框架和朋友制作了一些非常简洁的Java代码,但我遇到了一个看似简单的情况,很难做到简洁。

考虑一个和方法。我想将s映射到s并得到第一个.List<Thing> thingsOptional<Other> resolve(Thing thing)ThingOptional<Other>Other

显而易见的解决方案是使用 ,但要求您返回一个流,并且没有方法(或者它是一个或提供一个方法将其转换为或作为一个查看)。things.stream().flatMap(this::resolve).findFirst()flatMapOptionalstream()CollectionCollection

我能想到的最好的是这个:

things.stream()
    .map(this::resolve)
    .filter(Optional::isPresent)
    .map(Optional::get)
    .findFirst();

但对于一个非常普遍的案例来说,这似乎非常冗长。

有人有更好的想法吗?


答案 1

Java 9

Optional.stream 已添加到 JDK 9 中。这使您能够执行以下操作,而无需任何帮助程序方法:

Optional<Other> result =
    things.stream()
          .map(this::resolve)
          .flatMap(Optional::stream)
          .findFirst();

爪哇 8

是的,这是API中的一个小漏洞,因为将一个变成零或一长度有点不方便。你可以这样做:Optional<T>Stream<T>

Optional<Other> result =
    things.stream()
          .map(this::resolve)
          .flatMap(o -> o.isPresent() ? Stream.of(o.get()) : Stream.empty())
          .findFirst();

但是,在 内部使用三元运算符有点麻烦,因此最好编写一个小的辅助函数来执行此操作:flatMap

/**
 * Turns an Optional<T> into a Stream<T> of length zero or one depending upon
 * whether a value is present.
 */
static <T> Stream<T> streamopt(Optional<T> opt) {
    if (opt.isPresent())
        return Stream.of(opt.get());
    else
        return Stream.empty();
}

Optional<Other> result =
    things.stream()
          .flatMap(t -> streamopt(resolve(t)))
          .findFirst();

在这里,我内联了调用,而不是单独的操作,但这是一个品味问题。resolve()map()


答案 2

我正在根据用户srborlongan提出的编辑将第二个答案添加到我的其他答案中。我认为提出的技术很有趣,但它并不适合作为我的答案的编辑。其他人同意了,提议的编辑被否决了。(我不是选民之一。不过,这项技术有其优点。如果srborlongan发布他/她自己的答案,那将是最好的。这还没有发生,我不希望这项技术在StackOverflow拒绝的编辑历史的迷雾中丢失,所以我决定自己把它作为一个单独的答案出现。

基本上,该技术是以聪明的方式使用某些方法,以避免必须使用三元运算符()或if/else语句。Optional? :

我的内联示例将以这种方式重写:

Optional<Other> result =
    things.stream()
          .map(this::resolve)
          .flatMap(o -> o.map(Stream::of).orElseGet(Stream::empty))
          .findFirst();

使用帮助器方法的我的示例将按以下方式重写:

/**
 * Turns an Optional<T> into a Stream<T> of length zero or one depending upon
 * whether a value is present.
 */
static <T> Stream<T> streamopt(Optional<T> opt) {
    return opt.map(Stream::of)
              .orElseGet(Stream::empty);
}

Optional<Other> result =
    things.stream()
          .flatMap(t -> streamopt(resolve(t)))
          .findFirst();

评论

让我们直接比较原始版本与修改版本:

// original
.flatMap(o -> o.isPresent() ? Stream.of(o.get()) : Stream.empty())

// modified
.flatMap(o -> o.map(Stream::of).orElseGet(Stream::empty))

原始方法是一种直截了当的、如果熟练的方法:我们得到一个;如果它有一个值,我们返回一个包含该值的流,如果它没有值,我们返回一个空流。非常简单,易于解释。Optional<Other>

这种修改是聪明的,并且具有避免条件的优点。(我知道有些人不喜欢三元算子。如果误用,它确实会使代码难以理解。但是,有时事情可能太聪明了。修改后的代码也以 .然后它调用,其定义如下:Optional<Other>Optional.map

如果存在值,则对其应用提供的映射函数,如果结果为非 null,则返回描述结果的 Optional。否则,返回空的 Optional。

该调用返回 .如果输入 Optional 中存在一个值,则返回的 Optional 将包含一个 Stream,其中包含单个 Other 结果。但是,如果该值不存在,则结果为空的 Optional。map(Stream::of)Optional<Stream<Other>>

接下来,调用 将返回类型为 的值。如果其输入值存在,则获取该值,即单元素 。否则(如果输入值不存在),它将返回空 。因此,结果是正确的,与原始条件代码相同。orElseGet(Stream::empty)Stream<Other>Stream<Other>Stream<Other>

在讨论我的答案的评论中,关于被拒绝的编辑,我将这种技术描述为“更简洁,但也更晦涩”。我支持这一点。我花了一段时间才弄清楚它在做什么,我也花了一段时间写下上面关于它在做什么的描述。关键的微妙之处在于从 到 的转换。一旦你摸索了这一点,这是有道理的,但对我来说并不明显。Optional<Other>Optional<Stream<Other>>

不过,我要承认,随着时间的推移,最初晦涩难懂的东西可能会变成习语。这种技术可能最终成为实践中最好的方法,至少在被添加之前(如果它曾经这样做的话)。Optional.stream

更新:已添加到 JDK 9。Optional.stream


推荐