peek() 是 Stream 的惰性中间操作,用于对每个元素执行副作用(如打印、日志),不改变元素、不终止流,仅适用于调试监控,需终端操作触发,不可替代 filter 或 map。

peek() 是 Stream 的中间操作,它允许你对流中每个元素执行副作用(比如打印、日志、调试),但不会改变元素本身,也不会终止流。 它的核心作用就是“看一眼,不拿走”,适合调试和监控,而不是处理逻辑。
peek 的基本用法和限制
peek 接收一个 Consumer<T>,对每个流经的元素执行操作,然后原样向下传递。它不能修改元素内容(因为 Consumer 没有返回值),也不能跳过或替换元素。
- ✅ 正确:打印当前元素、记录日志、触发断点调试
- ❌ 错误:试图在 peek 里修改对象状态来“影响后续逻辑”(虽可能生效,但违背函数式设计原则,且不可靠)
- ❌ 错误:把它当 filter 或 map 用(它不改变流结构,也不过滤)
常见调试场景示例
比如想查看 filter 前后各有哪些元素:
list.stream()
.peek(System.out::println) // 查看原始数据
.filter(x -> x > 5)
.peek(x -> System.out.println("→ 通过 filter: " + x)) // 查看被保留的元素
.map(String::valueOf)
.collect(Collectors.toList());
输出会按流顺序穿插打印,帮你确认每步实际处理了什么。
立即学习“Java免费学习笔记(深入)”;
注意副作用的执行时机
peek 不是立即执行的——它像 map、filter 一样是惰性的。只有遇到终端操作(如 collect、forEach、count)时,整个流水线才真正运行,peek 中的代码才会被调用。
- 如果流没触发终端操作,peek 里的代码一句都不会执行
- 如果流被短路(如 findFirst 遇到第一个匹配就停),peek 只对已处理的元素生效
替代 peek 的更清晰写法(按需选择)
如果目标不是调试,而是需要“观察+转换”,优先考虑语义明确的操作:
- 想记录再转换?用 map(x -> { log(x); return transform(x); })
- 想条件性记录?用 filter(x -> { if (needLog(x)) log(x); return pred.test(x); })(不推荐,破坏纯净性)
- 真要调试?peek 最简洁;真要生产级监控?考虑专门的 trace 工具或自定义 Spliterator


















