Java 8 流操作执行顺序

2022-09-02 11:26:39
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),操作,那么为什么输出没有像我解释的那样出现?


答案 1

流是基于拉取的。只有终端操作(如 )才会导致项目被消耗。collect

从概念上讲,这意味着 将询问来自 、 从 和 来自 和 来自 和 来自 流的项。collectlimitlimitmapmapfilterfilter

从示意性上讲,您的问题中的代码导致

collect
  limit (0)
    map
      filter
        stream (returns 1)
      /filter (false)
      filter
        stream (returns 2)
      /filter (true)
    /map (returns 4)
  /limit (1)
  limit (1)
    map
      filter
        stream (returns 3)
      /filter (false)
      filter
        stream (returns 4)
      /filter (true)
    /map (returns 16)
  /limit (2)
  limit (2)
  /limit (no more items; limit reached)
/collect

这符合您的第一个打印输出。


答案 2

这是中间流操作的延迟执行/评估的结果。

操作链以从 到 的反向顺序懒惰地计算,一旦前一步生成值,每个步骤就会消耗值。collect()filter()

为了更清楚地描述正在发生的事情:

  1. 唯一的终端操作开始对链的评估。collect()
  2. limit()开始评估其祖先
  3. map()开始评估其祖先
  4. filter()开始使用源流中的值
  5. 1被评估,被评估并产生第一个值2
  6. map()使用其祖先返回的第一个值,并生成一个值
  7. limit()消耗该价值
  8. collect()收集第一个值
  9. limit()需要来自源的另一个值map()
  10. map()需要来自其祖先的另一个值
  11. filter()恢复评估以产生另一个结果,并在评估和产生新值之后344
  12. map()消耗它并产生新的值
  13. limit()使用新值并返回它
  14. collect()收集最后一个值。

来自 java.util.stream docs

流操作分为中间操作和终端操作,并组合形成流管道。流管道由源(如集合、数组、生成器函数或 I/O 通道)组成;然后是零个或多个中间操作,如 Stream.filter 或 Stream.map;和终端操作,如 Stream.forEach 或 Stream.reduce。

中间操作返回新流。他们总是懒惰;执行中间操作(如 filter())实际上并不执行任何筛选,而是创建一个新流,该流在遍历时包含与给定谓词匹配的初始流的元素。在执行管道的终端操作之前,不会开始遍历管道源。