flatMap是否保证是懒惰的? [重复]

问题描述 投票:7回答:3

这个问题在这里已有答案:

请考虑以下代码:

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

当第一个足够时,fetchDataFromInternet会被叫到第二个网址吗?

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

    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

答:根据以下评论和答案,flatmap部分是懒惰的。即完全读取第一个流,只有在需要时才会读取下一个流。阅读流是急切的,但阅读多个流是懒惰的。

如果打算这样做,API应该让函数返回Iterable而不是流。

换句话说:link

java java-8 java-stream flatmap
3个回答
11
投票

在目前的实施下,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将不得不等待所有10_000计算,即使你只关心一个。

编辑

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

编辑2

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


5
投票

目前尚不清楚为什么你设置一个不能解决实际问题的例子,你感兴趣的。如果你想知道,当应用像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中已经讨论过的那样。

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


1
投票

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

 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

© www.soinside.com 2019 - 2024. All rights reserved.