flatMap保证是懒惰的吗?

2022-09-04 08:44:33

请考虑以下代码:

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

当第一个就足够了时,会调用第二个URL吗?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,如果这对实施很重要

:根据下面的评论和答案,平面图部分懒惰。即完全读取第一个流,并且仅在需要时,它才会进行下一个流。读取流是渴望的,但读取多个流是懒惰的。

如果此行为是有意的,API 应允许函数返回流而不是流。Iterable

换句话说:链接


答案 1

在目前的实施下,是急切的;像任何其他有状态的中间操作(如 和 )一样。而且很容易证明:flatmapsorteddistinct

 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();

这意味着对于每个 ,即使您关心单个操作,也会阻止其之后的所有其他操作。因此,让我们假设从单个生成行开始,即使您只关心一个,您也必须等待所有10_000被计算出来。urlflatMapurlfetchDataFromInternet(url)10_000findFirst

编辑

这在Java 10中得到了修复,我们找回了懒惰:参见JDK-8075939

编辑 2

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


答案 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

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


推荐