stream.peek()是调试流式操作最轻量的方式,它作为中间操作在每个元素经过时执行指定动作(如打印日志)却不改变数据,必须置于终止操作前才生效,常用于观察过滤、映射等中间状态。

Stream.peek() 是调试流式操作最轻量的方式,它不改变数据,只在每个元素经过时执行你指定的操作,比如打印日志。
peek 的核心作用:观察中间状态
Stream 是惰性求值的,中间操作(如 filter、map)不会立即执行,只有遇到终止操作(如 collect、forEach)才真正触发。peek 就是在这个链条中“插个眼”,让你看到某个环节的元素长什么样。
注意:peek 必须出现在终止操作之前,否则什么都不会打印。
基本用法:打印当前元素
直接传入 Lambda 表达式,用 System.out.println 输出:
List<string> list = Arrays.asList("apple", "banana", "cherry");
list.stream()
.filter(s -> s.length() > 5)
.peek(s -> System.out.println("过滤后拿到:" + s))
.map(String::toUpperCase)
.peek(s -> System.out.println("转大写后:" + s))
.collect(Collectors.toList());</string>
输出会是:
过滤后拿到:banana 转大写后:BANANA 过滤后拿到:cherry 转大写后:CHERRY
更实用的调试技巧
- 打印带索引的元素:配合 AtomicInteger 或 IntStream.range,适合定位第几个元素出问题
- 结合断点调试:在 peek 的 Lambda 里加一行 Debugger.breakpoint()(Java 14+),或直接在 IDE 中点击行号打断点(部分 IDE 支持)
-
只对特定元素打印:加条件判断,避免刷屏,例如
.peek(s -> { if (s.contains("err")) System.out.println("可疑项:" + s); }) - 避免副作用误用:peek 不该修改元素本身(如 s += "!"),也不该做业务逻辑(如调用 service.save()),它只是观察者
peek 和 forEach 的区别别搞混
forEach 是终止操作,执行完就结束了;peek 是中间操作,必须后面还有终止操作才能生效。下面这段代码 什么都不会打印:
list.stream().peek(System.out::println); // ❌ 没有终止操作,整条流根本没运行
而这样才可以:
list.stream().peek(System.out::println).count(); // ✅ count 是终止操作
替代方案:什么时候不该用 peek
- 需要记录完整执行路径(比如每个阶段耗时),建议用自定义工具方法包装流操作
- 调试复杂转换逻辑,不如把流拆成变量分步执行,再用普通日志或断点
- 生产环境禁用日志时,peek 里的 println 仍会执行 —— 应改用 SLF4J 等支持日志级别的方案
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











