当前位置: 首页 > 工具软件 > Peek > 使用案例 >

Java stream 中 peek() 的合理用法

施飞鸿
2023-12-01

背景

这周遇到了一件很有意思的事情。在看项目代码时,发现了这么一段:

return objects.stream()
	       .peek(object -> addInfo(object, someParams))
	       .collect(Collectors.toList());

因为之前没接触过 peek(),这段代码看得我云里雾里。

后来在 这个链接 里读到这样一句话:

On top of that, peek() can be useful in another scenario: when we want to alter the inner state of an element.

翻译过来就是:

除此之外,peek() 还有另一种用途:改变元素的内部状态

这样一来,就不难理解上面代码的意图了:对每个 object 执行 addInfo(object, someParams),然后把结果收集到一个 List 里。

但后来又看了一些讨论,发现这个用法在这里虽然不算错,但并不是 peek() 最合理的用法。

最终操作(terminal operation)

为什么呢?因为 peek() 并不是一个最终操作(terminal operation)。出于性能考虑,stream 被设计为“元素只有在最终操作需要时才会被处理”。如果没有最终操作的“拉动”,那么 stream 中就没有操作会真正执行。

在上面的例子中,.collect(Collectors.toList()) 就是一个最终操作,而且这个操作会“拉动”所有元素。这样一来,每个元素都一定会被应用 peek() 方法,所以放在这里没有问题。

但在使用 peek() 时仍然需要注意,因为它只保证作用于流经管道的元素,但并不保证全部元素都会流经管道。

peek() vs forEach()

forEach() 则是一个最终操作。除此之外,peek()forEach() 再无其他不同。

顾名思义,forEach() 就是保证对 stream 里的每个元素都应用某个方法。如果这是你的意图,那么最好用 forEach() 而不是 peek(),这样代码会便于理解。如果上面的代码用的是 forEach(),那我一开始也不至于看不懂了。

peek() 的典型用法:协助调试

正因为 peek() 不是一个最终操作,不会影响“哪些元素会流过”,所以十分适合在调试的时候,用来打印出流经管道的元素。例如:

Stream.of("one", "two", "three", "four")
         .filter(e -> e.length() > 3)
         .peek(e -> System.out.println("Filtered value: " + e))
         .map(String::toUpperCase)
         .peek(e -> System.out.println("Mapped value: " + e))
         .collect(Collectors.toList());

小结

  • 如果想对流经的每个元素应用一个函数,从而改变某些状态,那么请用 forEach()
  • 如果想打印流经的每个元素的状态(日志或 debug),这时应该用 peek()

参考链接

In Java streams is peek really only for debugging?
Non-terminal forEach() in a stream?

 类似资料: