为了账号安全,请及时绑定邮箱和手机立即绑定

java - 在Java Stream API中,中间操作被延迟执行而终端操作被急切执行是什么意思?

java - 在Java Stream API中,中间操作被延迟执行而终端操作被急切执行是什么意思?

UYOU 2021-12-01 16:12:42
list.stream().filter( a-> a < 20 && a > 7).forEach(a -> System.out.println(a));fiter 被懒惰地执行。forEach 被急切地处决。这意味着什么?
查看完整描述

3 回答

?
长风秋雁

TA贡献1757条经验 获得超7个赞

假设您进行了以下操作。


list.stream()

    .map(a -> a * a)

    .filter(a -> a > 0 && a < 100)

    .map(a -> -a)

    .forEach(a -> System.out.println(a));

中间操作是映射和过滤器,终端操作是forEach. 如果急切地执行中间操作,.map(a -> a * a)则将立即映射整个流,并将结果传递给.filter(a -> a > 0 && a < 10)which 将立即过滤结果,然后将传递给.map(a -> -a)which 将映射过滤后的结果,然后将其传递给forEach然后立即打印流中的每个元素。


然而,中间操作不是急切的,而是懒惰的。这意味着序列


list.stream()

    .map(a -> a * a)

    .filter(a -> a > 0 && a < 100)

    .map(a -> -a)

实际上并没有立即做任何事情。它只是创建一个新的流来记住它应该执行的操作,但直到实际产生结果时才真正执行它们。直到forEach尝试从流中读取一个值,然后它才会转到原始流,获取一个值,使用 映射它a -> a * a,过滤它,如果它通过过滤器,映射它使用a -> -a,然后将该值传递给forEach。


这就像在餐厅工作的人被赋予了从脏堆中取出所有盘子,清洗它们,将它们堆叠起来,然后在厨师准备上菜时将它们交给厨师的工作。人急了,就立刻把整堆脏盘子拿起来,一下子洗干净,叠好,等厨子要盘子时,一一递给上菜。


然而,懒惰的员工会意识到厨师一次只需要一个盘子,而且只有在食物准备好时才需要。因此,当厨师需要一个盘子时,员工只需从一堆盘子中取出一个盘子,清洗干净并递给厨师,一个接一个,直到所有盘子都洗干净,所有的食物都端上来为止。


那么有什么好处呢?


一个主要优点是惰性方法大大改善了延迟。您可能知道,程序的单个线程一次只能做一件事。进一步扩展类比,假设有大约 800 个盘子,但厨师实际上必须等待洗衣机洗完盘子,然后再将一个递给他。如果热心的洗碗工坚持先把盘子洗干净再递过来,厨师就得等着800个盘子都洗干净了,然后一次上菜800顿,到时候愤怒的顾客都已经离开了。


然而,有了懒惰的洗衣机,厨师每上菜,他只需要等一个盘子。因此,如果洗盘子需要 10 秒,而上菜几乎是即时的,那么在场景 1 中,所有餐点都会一次上菜,但只有在等待两个多小时后才能上菜。但在场景 2 中,每顿饭的供应间隔约为 10 秒。因此,即使提供所有餐点所需的时间相同,场景 2 肯定更可取。


我在此将类比扩展了一点,但希望这可以帮助您更好地理解它。


查看完整回答
反对 回复 2021-12-01
?
撒科打诨

TA贡献1934条经验 获得超2个赞

延迟执行意味着操作只会在必要时执行。

急切执行意味着操作将立即执行。

那么你可能会问什么时候执行惰性中间操作?

当对管道应用终端操作(Eager 操作)时。

那么我们如何知道一个操作是中间的(懒惰的)还是终端的(急切的)呢?

当操作返回一个Stream<T>whereT可以是任何类型时,它就是一个中间操作(懒惰);如果操作返回任何其他内容,即 void、int、boolean 等,那么它是终端(急切)操作。


查看完整回答
反对 回复 2021-12-01
?
慕沐林林

TA贡献2016条经验 获得超9个赞

在的JavaDoc的Stream说:


流是懒惰的;对源数据的计算仅在终端操作启动时进行,源元素仅在需要时消费。


关于中间操作的JavaDoc:


他们总是懒惰;执行中间操作,例如 filter()实际上并不执行任何过滤,而是创建一个新的流,当遍历时,该流包含与给定谓词匹配的初始流的元素。管道源的遍历直到管道的终端操作被执行后才开始。


由于map是一个惰性操作,以下代码将不打印任何内容:


Stream.of(1, 2, 3).map(i -> {

    System.out.println(i);

    return i;

});

这Stream缺少将执行它的终端操作,这将调用中间操作。


类似list.stream().filter( a-> a > 20 && a < 7)将返回一个Stream但list尚未过滤的元素。


但即使执行了终端操作,还有更多关于懒惰的问题:


懒惰还可以避免在不必要时检查所有数据;对于诸如“查找第一个长度超过 1000 个字符的字符串”之类的操作


如果需要执行惰性操作来确定 a 的结果,则会执行惰性操作Stream。并非来自源的所有元素都必须由惰性操作处理。


关于终端操作的 JavaDoc:


在几乎所有情况下,终端操作都是急切的,在返回之前完成对数据源的遍历和管道的处理。


此外,一个Stream.


执行完终端操作后,流管道被认为已被消耗,不能再使用;


继续这个例子:


long count = Stream.of(1, 2, 3).map(i -> {

    System.out.println(i);

    return i;

}).count();

确定count映射是无关紧要的。因此,此代码仍然不会打印任何内容。但由于count()是一个终端操作,流被处理并count获得3分配的值。


如果我们将终端操作更改为,.min(Comparator.naturalOrder());则执行所有映射,我们将看到打印的整数。


查看完整回答
反对 回复 2021-12-01
  • 3 回答
  • 0 关注
  • 253 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信