Java 8流操作执行顺序

Bra*_*avo 11 java collections java-8 java-stream

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);
}
Run Code Online (Sandbox Code Playgroud)

当执行时,输出下面的逻辑来了

filtering 1
filtering 2
mapping 2
filtering 3
filtering 4
mapping 4
4
16
Run Code Online (Sandbox Code Playgroud)

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

filtering 1
filtering 2
filtering 3
filtering 4
mapping 2
mapping 4
4
16
Run Code Online (Sandbox Code Playgroud)

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

Mar*_*eel 20

流是基于拉的.只有终端操作(如collect)会导致消耗物品.

从概念上讲,这意味着collect将要求一个项目从limit,limitmapmapfilter,并filter从流.

示意性地,您的问题中的代码会导致

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
Run Code Online (Sandbox Code Playgroud)

这符合您的第一次打印输出.


Umb*_*ndi 9

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

操作的链以相反的顺序从去被懒惰地评估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()恢复评估以产生另一个结果并在评估34产生新值之后4
  12. map() 消耗它并产生新的价值
  13. limit() 使用新值并返回它
  14. collect() 收集最后一个值.

来自java.util.stream文档:

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

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


Era*_*ran 3

您注意到的行为是正确的。为了查明某个数字是否通过了整个 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
Run Code Online (Sandbox Code Playgroud)

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