takeWhile, dropWhile 惰性 java9

takeWhile, dropWhile laziness java9

在 scala 中,这些方法工作正常,但在 java9 dropWhile 中,我认为工作方式不同。

这里是 takeWhile 的例子

Stream.of("a", "b", "c", "de", "f", "g", "h")
                .peek(System.out::println)
                .takeWhile(s -> s.length() <= 1)
                .collect(Collectors.toList());

输出正常: a, b, c, de, [a, b, c] 它不处理 "de" 之后的元素,因此它按预期工作

但是 dropWhile 的工作方式与我预期的不同:

Stream.of("a", "b", "c", "de", "f", "g", "h")
                .peek(s -> System.out.print(s + ", "))
                .dropWhile(s -> s.length() <= 1)
                .collect(Collectors.toList());

输出为: a, b, c, de, f, g, h, [de, f, g, h]

所以它不会在 "de" 个元素之后停止,它正在处理整个集合。

为什么要处理整个集合?我知道,它需要获取所有元素并将其收集到列表中,但它不应该在 "de" 元素之后停止处理吗?

这是预期的行为,与它在 Scala 中的工作方式相同,dropWhile 处理整个流。

dropWhile 是 takeWhile 的逆过程。当条件变为假时,takeWhile 停止处理。 dropWhile 处理整个流,但只在条件为真时不传递任何元素。一旦条件变为假,dropWhile 将传递所有剩余元素,无论条件是真还是假。

TL;DR

始终查看整个流,尤其是终端操作以判断管道的行为。 collect 对打印元素的影响与 takeWhiledropWhile.

一样大

详细

我不确定您是否不同意输出或结果列表。在任何情况下,处理流的既不是 takeWhile 也不是 dropWhile,而是终端操作,在本例中为 collect。它的任务是收集流中的所有元素,因此通过它收集 "pulls" 个元素,直到流报告不包含更多元素。

一旦条件第一次变成falsetakeWhile就是这种情况。在报告没有更多元素剩余后,collect 停止提取元素,因此输入流的处理停止,来自 peek.

的消息也停止。

不过 dropWhile 不同。在第一次请求它时,它 "fasts forward" 通过输入流,直到条件第一次变为 false (这使得 peek 打印 a, b, c, de。任何第一个元素它看到之后的操作是de。从那时起collect继续通过流拉取元素,直到它被报告为空,一旦输入流以h结束时就是这种情况,这导致到 peek.

的其余输出

尝试以下操作,结果应该是 a, b, c, de [de]。 (我很确定,我什至没有尝试过。;))

Stream.of("a", "b", "c", "de", "f", "g", "h")
                .peek(s -> System.out.print(s + ", "))
                .dropWhile(s -> s.length() <= 1)
                .findFirst();

看来,对peek的工作原理存在根本性的误解。它不与下一个后续链接操作相关联,如 dropWhile,而是与它后面的整个 Stream 管道相关联。并且不区分“处理元素”和“取所有元素”

所以简单的代码

Stream.of("a", "b", "c", "de", "f", "g", "h")
      .peek(System.out::println)
      .collect(Collectors.toList());

“获取所有元素”,但在它们从流源传递到收集器时打印它们。

在您的示例中,无论元素是传递给 dropWhile 的谓词还是直接传递给 Collector 都没有区别,无论哪种情况,都会由 [= 报告13=]操作放在both.

之前

如果你使用

Stream.of("a", "b", "c", "de", "f", "g", "h")
      .dropWhile(s -> {
          System.out.println("dropWhile: "+s);
          return s.length() <= 1;
      })
      .peek(s -> System.out.println("collecting "+s))
      .collect(Collectors.toList());

相反,它会打印

dropWhile: a
dropWhile: b
dropWhile: c
dropWhile: de
collecting de
collecting f
collecting g
collecting h

显示 dropWhile 的谓词的计算如何在第一个未接受的元素之后停止,而向 Collector 的传输从该元素开始。

这与 takeWhile 不同,其中 both,谓词求值和收集器停止消费元素,因此没有消费者留下,整个 Stream 管道可以停止迭代源。

我可能会在这里陈述显而易见的事情,但这对我来说很有意义,因此也可能对您有所帮助。

takeWhile 获取前 n 个元素,直到命中谓词(它 return 为真)。因此,如果您假设传入流中有 7 个元素,并且 takeWhile 对前 3 个元素 return 为真,则生成的流将包含 3 个元素。

你可以这样想:

Stream result = Stream.empty();
while(predicate.apply(streamElement)){ // takeWhile predicate
   result.add(streamElement);
   streamElement = next(); // move to the next element
}
return result; 

dropWhile 表示删除元素直到谓词被命中(它 return 为真)。那么在删除 3 个元素之后,您的结果流会是什么样子?要知道我们需要迭代所有其他元素,这就是 peek 报告其余部分的原因。

我也喜欢从 Set(而不是 List)中 dropWhile 的类比。假设这是你的集合:

 Set<String> mySet = Set.of("A", "B", "CC", "D");

你会在这里用相同的谓词做一个 dropWhile(因为它不是一个列表而且你没有遇到顺序);在 dropWhile 操作之后,除非您迭代剩下的所有元素,否则无法知道结果。