【问题标题】:Is flatMap guaranteed to be lazy? [duplicate]flatMap 保证是懒惰的吗? [复制]
【发布时间】:2018-02-27 13:24:34
【问题描述】:

考虑以下代码:

urls.stream()
    .flatMap(url -> fetchDataFromInternet(url).stream())
    .filter(...)
    .findFirst()
    .get();

当第一个网址足够时,是否会为第二个网址调用fetchDataFromInternet

我尝试了一个较小的示例,它看起来像预期的那样工作。即一个一个地处理数据,但可以依赖这种行为吗?如果没有,在.flatMap(...) 之前调用.sequential() 有帮助吗?

    Stream.of("one", "two", "three")
            .flatMap(num -> {
                System.out.println("Processing " + num);
                // return FetchFromInternetForNum(num).data().stream();
                return Stream.of(num);
            })
            .peek(num -> System.out.println("Peek before filter: "+ num))
            .filter(num -> num.length() > 0)
            .peek(num -> System.out.println("Peek after filter: "+ num))
            .forEach(num -> {
                System.out.println("Done " + num);
            });

输出:

Processing one
Peek before filter: one
Peek after filter: one
Done one
Processing two
Peek before filter: two
Peek after filter: two
Done two
Processing three
Peek before filter: three
Peek after filter: three
Done three

更新:如果对实施很重要,请使用官方的 Oracle JDK8

回答: 根据 cmets 和下面的答案,flatmap 是部分懒惰的。即完全读取第一个流,并且仅在需要时才进行下一个。读取流是急切的,但读取多个流是懒惰的。

如果这种行为是有意的,API 应该让函数返回 Iterable 而不是流。

换句话说:link

【问题讨论】:

  • parallelism 上的文档说“当您创建流时,除非另有说明,否则它始终是串行流。”因此不需要调用 .sequential()
  • 是什么让你认为它不是?
  • @pedromss 文档没有明确说明。 docs.oracle.com/javase/8/docs/api/java/util/stream/… 看起来它可能不会偷懒的情况很少:stackoverflow.com/questions/29229373/…
  • @balki 您链接的 SO 帖子在接受的答案中指出中间操作总是惰性的。此外,来自documentation:“流是惰性的;仅在启动终端操作时才对源数据进行计算,并且仅在需要时使用源元素。” Flatmap 是一个中间操作
  • fetchDataFromInternet 不会被不必要地调用,但特定 fetchDataFromInternet 调用返回的元素可能会在没有惰性的情况下得到处理。

标签: java java-8 java-stream flatmap


【解决方案1】:

在当前的实现下flatmap 是急切的;像任何其他有状态的中间操作(如sorteddistinct)。而且很容易证明:

 int result = Stream.of(1)
            .flatMap(x -> Stream.generate(() -> ThreadLocalRandom.current().nextInt()))
            .findFirst()
            .get();

    System.out.println(result);

这永远不会结束,因为flatMap 被急切地计算。以您为例:

urls.stream()
    .flatMap(url -> fetchDataFromInternet(url).stream())
    .filter(...)
    .findFirst()
    .get();

这意味着对于每个urlflatMap 将阻止其后的所有其他操作,即使您关心单个操作。所以让我们假设从单个url 你的fetchDataFromInternet(url) 生成10_000 行,那么你的findFirst 将不得不等待all 10_000 被计算出来,即使你只关心一个.

编辑

这在 Java 10 中得到了修复,在那里我们恢复了我们的懒惰:参见 JDK-8075939

编辑 2

这在 Java 8 中也已修复 (8u222):JDK-8225328

【讨论】:

  • 看起来 Java 8 也是 backported
  • @ZhekaKozlov 感谢您提供的信息 - 如果您愿意,您也可以编辑答案。
【解决方案2】:

不清楚你为什么设置一个不解决你感兴趣的实际问题的例子。如果你想知道,当应用像findFirst()这样的短路操作时,处理是否是惰性的,好吧,然后使用一个使用findFirst() 而不是forEach 的示例来处理所有元素。此外,将日志语句直接放入要跟踪其评估的函数中:

Stream.of("hello", "world")
      .flatMap(s -> {
          System.out.println("flatMap function evaluated for \""+s+'"');
          return s.chars().boxed();
      })
      .peek(c -> System.out.printf("processing element %c%n", c))
      .filter(c -> c>'h')
      .findFirst()
      .ifPresent(c -> System.out.printf("found an %c%n", c));
flatMap function evaluated for "hello"
processing element h
processing element e
processing element l
processing element l
processing element o
found an l

这表明传递给flatMap 的函数会按预期延迟评估,而返回的(子)流的元素不会尽可能延迟评估,正如您已链接自己的the Q&A 中所讨论的那样。

因此,对于从传递给flatMap 的函数调用的fetchDataFromInternet 方法,您将获得所需的惰性。但不适用于它返回的数据。

【讨论】:

    【解决方案3】:

    今天我也偶然发现了这个错误。行为不是那么严格,因为简单的情况,如下所示,工作正常,但类似的生产代码不起作用。

     stream(spliterator).map(o -> o).flatMap(Stream::of)..flatMap(Stream::of).findAny()
    

    对于那些迫不及待地迁移到 JDK-10 的人来说,有另一种真正的惰性流。它不支持并行。它专门用于 JavaScript 翻译,但对我来说很有效,因为界面是一样的。

    StreamHelper 是基于集合的,但是很容易适配 Spliterator。

    https://github.com/yaitskov/j4ts/blob/stream/src/main/java/javaemul/internal/stream/StreamHelper.java

    【讨论】:

      猜你喜欢
      • 2012-08-12
      • 2018-01-28
      • 1970-01-01
      • 2017-01-21
      • 2011-03-13
      • 2019-02-19
      • 2021-08-12
      • 1970-01-01
      相关资源
      最近更新 更多