flatMap 保证是惰性的吗?
Is flatMap guaranteed to be lazy?
考虑以下代码:
urls.stream()
.flatMap(url -> fetchDataFromInternet(url).stream())
.filter(...)
.findFirst()
.get();
当第一个足够时,fetchDataFromInternet
会被调用第二个 url 吗?
我尝试了一个较小的例子,它看起来像预期的那样工作。即一个一个地处理数据,但是可以依赖这种行为吗?如果没有,在 .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 如果这对实现很重要
回答:
根据下面的评论和答案,平面图是部分惰性的。即完全读取第一个流,仅在需要时才读取下一个流。读取流是急切的,但读取多个流是惰性的。
如果此行为是有意的,API 应该让函数 return 成为 Iterable
而不是流。
换句话说:
在当前的实现下,flatmap
是急切的;像任何其他有状态的中间操作(如 sorted
和 distinct
)。而且很容易证明:
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();
这意味着对于每个 url
,flatMap
将阻止其后的所有其他操作,即使您只关心其中一个。因此,让我们假设从单个 url
你的 fetchDataFromInternet(url)
生成 10_000
行,那么你的 findFirst
将不得不等待 all 10_000 进行计算,即使您只关心一个。
编辑
这在 Java 10 中得到了修复,我们在这里找回了懒惰:请参阅 JDK-8075939
编辑 2
这也在 Java 8 (8u222) 中得到修复:JDK-8225328
不清楚你为什么设置一个没有解决实际问题的例子,你感兴趣。如果你想知道,在应用短路操作时处理是否懒惰 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
的函数得到了预期的延迟评估,而返回的(子)流的元素没有尽可能延迟评估,正如 中已经讨论的那样您已链接自己。
因此,关于从传递给 flatMap
的函数调用的 fetchDataFromInternet
方法,您将获得所需的惰性。但不是针对数据 returns.
今天我也无意中发现了这个错误。行为不是那么直截了当,导致简单的情况,如下所示,工作正常,但类似的生产代码不起作用。
stream(spliterator).map(o -> o).flatMap(Stream::of)..flatMap(Stream::of).findAny()
对于不能再等几年再迁移到 JDK-10 的人来说,还有另一种真正的懒惰流。它不支持并行。它专用于 JavaScript 翻译,但它对我有用,因为界面是一样的。
StreamHelper 是基于集合的,但很容易适配 Spliterator。
考虑以下代码:
urls.stream()
.flatMap(url -> fetchDataFromInternet(url).stream())
.filter(...)
.findFirst()
.get();
当第一个足够时,fetchDataFromInternet
会被调用第二个 url 吗?
我尝试了一个较小的例子,它看起来像预期的那样工作。即一个一个地处理数据,但是可以依赖这种行为吗?如果没有,在 .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 如果这对实现很重要
回答: 根据下面的评论和答案,平面图是部分惰性的。即完全读取第一个流,仅在需要时才读取下一个流。读取流是急切的,但读取多个流是惰性的。
如果此行为是有意的,API 应该让函数 return 成为 Iterable
而不是流。
换句话说:
在当前的实现下,flatmap
是急切的;像任何其他有状态的中间操作(如 sorted
和 distinct
)。而且很容易证明:
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();
这意味着对于每个 url
,flatMap
将阻止其后的所有其他操作,即使您只关心其中一个。因此,让我们假设从单个 url
你的 fetchDataFromInternet(url)
生成 10_000
行,那么你的 findFirst
将不得不等待 all 10_000 进行计算,即使您只关心一个。
编辑
这在 Java 10 中得到了修复,我们在这里找回了懒惰:请参阅 JDK-8075939
编辑 2
这也在 Java 8 (8u222) 中得到修复:JDK-8225328
不清楚你为什么设置一个没有解决实际问题的例子,你感兴趣。如果你想知道,在应用短路操作时处理是否懒惰 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
的函数得到了预期的延迟评估,而返回的(子)流的元素没有尽可能延迟评估,正如
因此,关于从传递给 flatMap
的函数调用的 fetchDataFromInternet
方法,您将获得所需的惰性。但不是针对数据 returns.
今天我也无意中发现了这个错误。行为不是那么直截了当,导致简单的情况,如下所示,工作正常,但类似的生产代码不起作用。
stream(spliterator).map(o -> o).flatMap(Stream::of)..flatMap(Stream::of).findAny()
对于不能再等几年再迁移到 JDK-10 的人来说,还有另一种真正的懒惰流。它不支持并行。它专用于 JavaScript 翻译,但它对我有用,因为界面是一样的。
StreamHelper 是基于集合的,但很容易适配 Spliterator。