peek() 是 Stream API 中专为调试设计的无副作用中间操作,仅在终端操作执行时触发,用于打印日志、设断点等,不可修改元素或用于业务逻辑。

Stream API 的链式调用让代码简洁,但也让调试变难——中间步骤无法直接观察。peek() 是专为此设计的“无副作用”调试工具,它不改变流元素,只允许你在每个元素经过时执行自定义操作(比如打印、打日志、断点),是定位流处理逻辑问题最轻量、最安全的方式。
peek() 的核心特性与使用前提
peek() 是 Stream 的中间操作,返回新流,但仅在流实际执行终端操作(如 collect、forEach、count)时才触发。它不会提前消费流,也不会影响流中元素本身。关键约束有两点:
- 必须配合终端操作才能生效;单独写 peek() 不会输出任何内容
- 不能修改流中元素(否则违反“无副作用”约定,可能引发不可预期行为)
- 适合调试,不适合业务逻辑(例如不该在 peek 里做数据库写入或状态变更)
常见调试场景与 peek 写法示例
以下是在过滤、映射、去重等环节插入 peek 的典型方式,便于快速确认数据流转是否符合预期:
// 原始链路:过滤偶数 → 平方 → 去重 → 收集
List
.filter(x -> x % 2 == 0) // 留下 2, 2, 4, 4
.peek(x -> System.out.println("after filter: " + x))
.map(x -> x * x) // 变成 4, 4, 16, 16
.peek(x -> System.out.println("after map: " + x))
.distinct() // 去重后剩 4, 16
.peek(x -> System.out.println("after distinct: " + x))
.collect(Collectors.toList());
结合日志框架与断点的进阶用法
生产环境避免用 System.out,推荐集成 SLF4J 或 Log4j:
立即学习“Java免费学习笔记(深入)”;
- 用 logger.debug("peek: {}", value) 替代 println,并确保日志级别为 DEBUG
- 在 peek 的 lambda 内部设断点(IDE 支持),可逐个检查每个元素的值、类型、状态
- 对复杂对象,peek 中调用 toString() 或字段抽取(如 peek(u -> log.info("user id: {}", u.getId())))更清晰
注意 peek 的局限性与替代方案
peek 无法捕获异常、不能查看短路操作(如 findFirst 后未执行的后续元素)、也不适用于并行流中确定性日志顺序。遇到这些情况可考虑:
- 临时改用 forEach(但会消费流,不能再链式继续)
- 拆分 Stream 链,用变量保存中间结果再调试(牺牲简洁性,换可读性)
- 使用第三方工具如 vavr 的 debug(),或自定义装饰型 Stream 包装器


















