将 Java 8 的 Optional 与 Stream::flatMap 结合使用

新手上路,请多包涵

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

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

The obvious solution would be to use things.stream().flatMap(this::resolve).findFirst() , but flatMap requires that you return a stream, and Optional doesn’t have a stream() 方法(或者它是 Collection 还是提供一种方法将其转换为或将其视为 Collection )。

我能想到的最好的是:

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

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

有人有更好的主意吗?

原文由 Yona Appletree 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 609
2 个回答

Java 9

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

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

Java 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() 操作,但这是个人喜好问题。

原文由 Stuart Marks 发布,翻译遵循 CC BY-SA 4.0 许可协议

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

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

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

 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 定义如下:

如果存在一个值,则将提供的映射函数应用于它,如果结果为非空,则返回一个描述结果的 Optional。否则返回一个空的 Optional。

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

接下来,调用 orElseGet(Stream::empty) 返回类型为 Stream<Other> --- 的值。如果它的输入值存在,则它获取值,即单个元素 Stream<Other> 。否则(如果输入值不存在)它返回一个空的 Stream<Other> 。所以结果是正确的,和原来的条件代码一样。

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

不过,我承认,随着时间的推移,最初晦涩难懂的事情可能会变得习以为常。可能这种技术最终成为实践中的最佳方法,至少在添加 Optional.stream 之前(如果有的话)。

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

原文由 Stuart Marks 发布,翻译遵循 CC BY-SA 3.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题