List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8);
List<Integer> twoEvenSquares = numbers.stream().filter(n -> {
System.out.println("filtering " + n);
return n % 2 == 0;
}).map(n -> {
System.out.println("mapping " + n);
return n * n;
}).limit(2).collect(Collectors.toList());
for(Integer i : twoEvenSquares)
{
System.out.println(i);
}
执行时输出的逻辑
filtering 1
filtering 2
mapping 2
filtering 3
filtering 4
mapping 4
4
16
如果流遵循短路概念(我们使用限制流操作),则输出必须如下所示:
filtering 1
filtering 2
filtering 3
filtering 4
mapping 2
mapping 4
4
16
因为在过滤2之后,我们还要找到另外一个元素来分层限制(2),操作,那么为什么输出不像我解释的那样?
答案 0 :(得分:19)
Streams是基于拉式的。只有终端操作(如collect
)才会导致使用项目。
从概念上讲,这意味着collect
会询问来自limit
的{{1}},limit
和来自map
的{{1}}的项目,来自流的map
。
示意图中您的问题中的代码会导致
filter
这符合你的第一次打印输出。
答案 1 :(得分:9)
这是中间流操作的延迟执行/评估的结果。
从collect()
到filter()
以相反的顺序对操作链进行延迟评估,每一步的值都会在上一步生成后立即消耗。
更清楚地描述正在发生的事情:
collect()
开始评估链。limit()
开始评估其祖先map()
开始评估其祖先filter()
开始使用源流中的值1
,评估2
并生成第一个值map()
使用其祖先返回的第一个值并生成一个值limit()
消耗该值collect()
收集第一个值limit()
需要map()
来源map()
需要其祖先的另一个值filter()
恢复评估以产生另一个结果,并在评估3
和4
后生成新值4
map()
使用它并生成新值limit()
使用新值并将其返回collect()
收集最后一个值。流操作分为中间和终端 操作,并组合形成流管道。一条小溪 管道由一个源(如Collection,一个数组,一个 生成器功能,或I / O通道);然后是零或更多 中间操作,如Stream.filter或Stream.map;和a 终端操作,如Stream.forEach或Stream.reduce。
中间操作返回一个新流。 他们总是懒惰; 执行中间操作,例如filter()实际上并不执行 执行任何过滤,而是创建一个新的流,当时 遍历,包含匹配的初始流的元素 给出谓词。管道源的遍历直到开始 执行管道的终端操作。
答案 2 :(得分:1)
您注意到的行为是正确的。为了找出一个数字是否通过整个Stream管道,您必须在所有管道步骤中运行该数字。
filtering 1 // 1 doesn't pass the filter
filtering 2 // 2 passes the filter, moves on to map
mapping 2 // 2 passes the map and limit steps and is added to output list
filtering 3 // 3 doesn't pass the filter
filtering 4 // 4 passes the filter, moves on to map
mapping 4 // 4 passes the map and limit steps and is added to output list
现在管道可以结束,因为我们有两个数字通过了管道。
答案 3 :(得分:1)
filter
和map
是中间操作。正如文件所述:
中间操作返回一个新流。他们总是懒惰; 执行诸如filter()之类的中间操作实际上并不是这样 执行任何过滤,而是创建一个新的流,当时 遍历,包含匹配的初始流的元素 给出谓词。管道来源的遍历不会开始直到 管道的终端操作已执行。
[...]
懒洋洋地处理流可以显着提高效率;在上面的filter-map-sum示例的管道中,过滤,映射和求和可以融合到数据的单个传递中,具有最小的中间状态。
所以当你调用你的终端操作(即collect()
)时,你可以想到这样的事情(这真的很简单(你将使用收集器来累积管道的内容, Streams不可迭代,...)并且不编译但只是为了可视化事物):
public List collectToList() {
List list = new ArrayList();
for(Elem e : this) {
if(filter.test(e)) { //here you see the filter println
e = mapping.apply(e); //here you see the mapping println
list.add(e);
if(limit >= list.size())
break;
}
}
return list;
}
答案 4 :(得分:1)
Stream
API并不意味着提供有关操作执行顺序的保证。这就是为什么你应该使用无副作用的功能。 “短路”并没有改变它的任何内容,只是没有执行超过必要的操作(并且在可能的有限时间内完成,即使对于无限流源也是如此)。当你看到你的输出时,你会发现一切正常。执行的操作与您期望的操作匹配,结果也是如此。
只有订单不匹配,这不是因为概念,而是你对实现的错误假设。但是如果你想一下不使用中间存储的实现如何看起来像,你会得出结论,它必须完全像观察。 Stream
将逐个处理每个项目,过滤,映射并在下一个项目之前收集它。