Peek()真正看到元素流过管道中的某个点

Vis*_*tna 3 java java-8 java-stream

我的问题以最简单的方式表达:

根据JavaDoc:

Peek()方法主要用于支持调试,您希望在元素流经管道中的某个点时查看元素.

我有一个管10米,并以距离37米 从输入头我有两个标记物[又名peek()]用于检查/调试我的元件.

现在从输入端我给出输入1,2,3,4,5.

在x = 4米处,我有一个filter()过滤所有小于等于的元素3.

现在按照Java doc我应该能够看到我在远程37米的管道输入发生了什么.

标记1距离3(.peek())处的输出应该不1,2,3,4,5应该是?? 标记2距离7处的输出 应该是4,5明显的.

但这实际上并没有发生,输出正在第一市场(.peek())正好1,2,3在第二市场即将到来4,5.


我为测试我的理论而执行的代码:

final List<Integer> IntList=
    Stream.of(1, 2, 3, 4, 5)
    .peek(it -> System.out.println("Before Filtering "+it)) // should print 1,2,3,4,5
    .filter(it -> it >= 3)
    .peek(it -> System.out.println("After Filtering: "+it)) //should print 4,5
    .collect(Collectors.toList());
Run Code Online (Sandbox Code Playgroud)

实际产量:

Before Filtering 1
Before Filtering 2
Before Filtering 3
After Filtering: 3
Before Filtering 4
After Filtering: 4
Before Filtering 5
After Filtering: 5
Run Code Online (Sandbox Code Playgroud)

预期输出(读取JavaDoc之后应该考虑的是什么(...主要用于支持调试,您希望在流经管道中的某个点时看到元素...)

    Before Filtering 1
    Before Filtering 2
    Before Filtering 3
    Before Filtering 4
    Before Filtering 5
    After Filtering: 4
    After Filtering: 5
Run Code Online (Sandbox Code Playgroud)

如果.peek()不仅仅是在管道中的特定点进行调试,那么def就不明确了.

对不起我的管道故事,我想这样我可以解释我最想问的问题.

ζ--*_*ζ-- 9

不需要.可以根据需要懒惰地评估流,并且操作顺序没有明确定义,尤其是当您peek()正在进行时.这允许流API支持非常大的流而不会浪费大量时间和内存,并且允许某些实现简化.特别是,在下一阶段之前,不需要对管道的单个阶段进行全面评估.

假设您的假设如下,假设以下代码有多浪费:

IntStream.range(1, 1000000).skip(5).limit(10).forEach(System::println);
Run Code Online (Sandbox Code Playgroud)

流以一百万个元素开始,最后为10.如果我们完全评估每个阶段,我们的中间体将分别为100万,999995和10个元素.

作为第二个示例,以下流不能一次评估一个阶段(因为IntStream.generate返回无限流):

IntStream.generate(/* some supplier */).limit(10).collect(Collectors.toList());
Run Code Online (Sandbox Code Playgroud)

您的管道确实通过第一个传递每个元素peek,然后通过第二个元素只传递一个子集peek.但是,管道以元素主要而不是阶段主要顺序执行此评估:它将管道评估为1,将其放在过滤器处,然后2.一旦评估管道为3,它就通过过滤器,因此两者都是偷看语句执行,然后发生4和5.


Bri*_*etz 8

Andrey Akhmetov的答案是正确的,但我想补充一下,因为这里有两个问题.一个是流管道语义的一般问题 - 这正是你的问题所在.次要的是关于的含义和限制peek().

对于主要问题 - 与之无关peek(),除了你是如何观察正在发生的事情的状态 - 你对流的直觉是完全错误的.没有理由相信:

collection.stream()
          .filter(x -> x.foo() > 3)
          .map(X::toBar)
          .forEach(b -> System.out.println("Bar: " + b);
Run Code Online (Sandbox Code Playgroud)

所有过滤都发生在所有打印之前的所有映射之前.该流可以按照自己喜欢的顺序自由交错过滤,映射和打印.(聚合中有一些排序保证.)这里的好处是,在无限流的某些情况下,这通常更高性能,更可并行化,更强大.只要您遵守规则(即,不依赖于另一个阶段的一个阶段的副作用),您将无法区分,除非您的代码运行得更快.

摇摆不定的语言的原因peek()是对于管道,如:

int size = collection.stream()
                     .map(...)
                     .peek(...)
                     .count()
Run Code Online (Sandbox Code Playgroud)

我们可以在不进行任何映射的情况下评估答案(因为map()已知是一个大小保持操作.)始终在peek()点处提供元素的要求会破坏许多有用的优化.因此,如果可以证明它不会影响答案,那么实现可以自由地忽略整个管道中间.(它可能产生较少的副作用,但如果你非常关心副作用,也许你不应该使用溪流.)

  • 中间操作按照它们在代码中出现的顺序进行应用。但是,有时它们可​​能会全部消失(例如,sortedSet.stream()。sorted()不会重新排序),并且如果没有答案就可以确定答案,则有时不需要从源中提取任何数据(例如,`list.stream()。map(f).count()`不需要绘制任何元素。) (2认同)