Stream.peek是专为观察设计的中间操作,不改变元素、不终止流,仅在终端操作触发时执行;其行为取决于在流链中的位置,需配合日志、避免副作用并确保终端操作存在。

Stream.peek 是专为观察设计的中间操作,它不改变元素、不终止流,只在终端操作触发时“顺路看一眼”。用对位置、避开副作用、配合结构化日志,才是稳定调试的关键。
找准 peek 的插入位置,看清你真正想观察的数据
peek 的输出内容完全取决于它在流链中的位置:
- 放在 filter 前:看到全部原始输入元素
- 放在 filter 后:只看到通过过滤的子集(数量变少,适合确认过滤逻辑)
- 放在 map 后:看到转换后的新值(比如字符串已转大写、数字已乘10),不是原始值
- 多个 peek 可叠加:每个都独立作用于当前节点的流元素,便于分步比对
用日志代替 print,避免阻塞和敏感泄露
System.out.println 是入门写法,但生产环境需升级:
- 用 SLF4J 或 Log4j 写 debug 级日志,通过日志级别开关控制是否生效
- 避免直接打印整个对象(如
log.debug("item: {}", item)),改用关键字段快照:log.debug("id={}, status={}, amount={}", item.getId(), item.getStatus(), item.getAmount()) - 字段可能为 null?提前判空或用 Optional.ofNullable 包裹,防止 peek 内部抛异常中断整条流
- 高吞吐场景加采样,例如每 100 条记录 1 条:
if (counter.incrementAndGet() % 100 == 0) log.debug(...)
严守“无副作用”边界,不碰状态、不调阻塞操作
peek 不是业务逻辑入口,它的稳定性依赖克制:
- 禁止修改传入对象的字段(如
item.setProcessed(true)),破坏不可变性且并行下线程不安全 - 禁止同步 I/O:不发 HTTP 请求、不写文件、不查数据库——这些会拖慢甚至卡死流
- 禁止抛出未捕获异常:Consumer 不支持受检异常,若内部出错(如 NPE),整个流立即终止
- 并行流中不依赖执行顺序:日志可能乱序,也不要用 ThreadLocal 存上下文
必须配终端操作,否则 peek 什么都不会做
这是最常被忽略的前提:
-
stream.peek(...).filter(...)单独写完就结束?不会输出任何内容 - 必须接上
.collect()、.forEach()、.count()、.findFirst()等终端操作,整条流水线才真正启动 - 短路操作(如 findFirst)可能只处理前几个元素,peek 也只触发对应次数,属正常行为


















