Java8流操作执行顺序

cgfeq70w  于 2021-07-13  发布在  Java
关注(0)|答案(5)|浏览(429)
  1. List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8);
  2. List<Integer> twoEvenSquares = numbers.stream().filter(n -> {
  3. System.out.println("filtering " + n);
  4. return n % 2 == 0;
  5. }).map(n -> {
  6. System.out.println("mapping " + n);
  7. return n * n;
  8. }).limit(2).collect(Collectors.toList());
  9. for(Integer i : twoEvenSquares)
  10. {
  11. System.out.println(i);
  12. }

执行时,下面的逻辑输出出现

  1. filtering 1
  2. filtering 2
  3. mapping 2
  4. filtering 3
  5. filtering 4
  6. mapping 4
  7. 4
  8. 16

如果流遵循短路概念(我们使用限制流操作),那么输出必须如下所示:

  1. filtering 1
  2. filtering 2
  3. filtering 3
  4. filtering 4
  5. mapping 2
  6. mapping 4
  7. 4
  8. 16

因为在过滤2之后,我们还需要再找到一个元素来分层限制(2),操作,所以为什么输出不是像我解释的那样呢?

0s7z1bwu

0s7z1bwu1#

这是中间流操作的延迟执行/评估的结果。
操作链是按照从 collect()filter() ,值在上一步生成后立即被每个步骤消耗。
为了更清楚地描述发生了什么:
唯一的终端操作 collect() 开始评估链。 limit() 开始评估其祖先 map() 开始评估其祖先 filter() 开始使用源流中的值 1 进行评估, 2 计算并生成第一个值 map() 使用其祖先返回的第一个值并生成一个值 limit() 消耗这个价值 collect() 收集第一个值 limit() 需要来自 map() 来源 map() 需要来自其祖先的另一个值 filter() 继续评估以产生另一个结果,并在评估之后 3 以及 4 产生新的价值
4 map() 消耗它并产生新的价值 limit() 使用新值并返回它 collect() 收集最后一个值。
从java.util.stream文档:
流操作分为中间操作和终端操作,并结合起来形成流管道。流管道由源(如集合、数组、生成器函数或i/o通道)组成;后跟零个或多个中间操作,如stream.filter或stream.map;以及stream.foreach或stream.reduce等终端操作。
中间操作返回一个新的流。他们总是懒惰;执行诸如filter()之类的中间操作实际上并不执行任何过滤,而是创建一个新的流,在遍历时,该流包含与给定 predicate 匹配的初始流元素。在执行管道的终端操作之前,不会开始遍历管道源。

8iwquhpp

8iwquhpp2#

这个 Stream api并不意味着提供有关操作执行顺序的保证。这就是为什么你应该使用无副作用的功能。“短路”并没有改变任何事情,它只是关于不执行超过必要的操作(并且尽可能在有限的时间内完成,甚至对于无限的流源)。当你查看你的输出时,你会发现一切正常。执行的操作与您期望的操作匹配,结果也匹配。
只是顺序不匹配,这不是因为概念,而是你对实现的错误假设。但是,如果您考虑一个不使用中间存储的实现必须是什么样子的,您就会得出结论:它必须完全像所观察到的那样。一 Stream 将一个接一个地处理每个项目,在下一个项目之前对其进行过滤、Map和收集。

p1tboqfb

p1tboqfb3#

你注意到的行为是正确的。为了确定某个数字是否通过整个流管道,必须在所有管道步骤中运行该数字。

  1. filtering 1 // 1 doesn't pass the filter
  2. filtering 2 // 2 passes the filter, moves on to map
  3. mapping 2 // 2 passes the map and limit steps and is added to output list
  4. filtering 3 // 3 doesn't pass the filter
  5. filtering 4 // 4 passes the filter, moves on to map
  6. mapping 4 // 4 passes the map and limit steps and is added to output list

现在管道可以结束了,因为我们有两个数字通过管道。

arknldoa

arknldoa4#

filter 以及 map 是中间操作。如文件所述:
中间操作返回一个新的流。他们总是懒惰;执行诸如filter()之类的中间操作实际上并不执行任何过滤,而是创建一个新的流,在遍历时,该流包含与给定 predicate 匹配的初始流元素。在执行管道的终端操作之前,不会开始遍历管道源。
[...]
缓慢地处理流可以显著提高效率;在上面的filter-map-sum示例这样的管道中,过滤、Map和求和可以融合到数据的一次传递中,中间状态最小。
所以当你调用终端操作(即 collect() ),您可以这样想(这是非常简化的(您将使用收集器来累积管道的内容,流是不可编辑的,…)并且不编译,但它只是可视化的东西):

  1. public List collectToList() {
  2. List list = new ArrayList();
  3. for(Elem e : this) {
  4. if(filter.test(e)) { //here you see the filter println
  5. e = mapping.apply(e); //here you see the mapping println
  6. list.add(e);
  7. if(limit >= list.size())
  8. break;
  9. }
  10. }
  11. return list;
  12. }
展开查看全部
bpzcxfmw

bpzcxfmw5#

流是基于拉的。只有终端操作(如 collect )将导致项目被消耗。
从概念上讲,这意味着 collect 将从 limit , limitmap 以及 mapfilter ,和 filter 从小溪里。
你问题中的代码示意性地导致

  1. collect
  2. limit (0)
  3. map
  4. filter
  5. stream (returns 1)
  6. /filter (false)
  7. filter
  8. stream (returns 2)
  9. /filter (true)
  10. /map (returns 4)
  11. /limit (1)
  12. limit (1)
  13. map
  14. filter
  15. stream (returns 3)
  16. /filter (false)
  17. filter
  18. stream (returns 4)
  19. /filter (true)
  20. /map (returns 16)
  21. /limit (2)
  22. limit (2)
  23. /limit (no more items; limit reached)
  24. /collect

这和你第一次打印出来的一致。

展开查看全部

相关问题