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 肯定更可取。
我在此将类比扩展了一点,但希望这可以帮助您更好地理解它。
TA贡献1934条经验 获得超2个赞
延迟执行意味着操作只会在必要时执行。
急切执行意味着操作将立即执行。
那么你可能会问什么时候执行惰性中间操作?
当对管道应用终端操作(Eager 操作)时。
那么我们如何知道一个操作是中间的(懒惰的)还是终端的(急切的)呢?
当操作返回一个Stream<T>
whereT
可以是任何类型时,它就是一个中间操作(懒惰);如果操作返回任何其他内容,即 void、int、boolean 等,那么它是终端(急切)操作。
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());则执行所有映射,我们将看到打印的整数。
添加回答
举报