Stream.iterate通过状态对象(如record Fib)封装递推变量,peek在map提取后记录中间值,全程保持流惰性与不可变性;错误做法包括拆分种子、iterate后直接peek、并行中修改状态或阻塞操作。

Stream.iterate 本身不递归,它通过状态演进模拟递推过程;peek 不参与计算,只负责在元素流经时“快照”当前值。两者配合的关键是:iterate 负责生成带上下文的状态流,peek 在恰当位置记录中间变量,全程不干扰流的惰性与不可变性。
明确 iterate 的状态封装逻辑
斐波那契这类递推序列必须把“前一项”和“后一项”打包成一个整体状态,不能拆成两个独立种子。常见错误是传入两个 long 值或试图在函数里写 if 判界——iterate 只认一个输入、一个输出。
- 用 record(Java 14+)最清晰:record Fib(long a, long b) {},种子为 new Fib(0L, 1L)
- 演进函数返回 new Fib(f.b, f.a + f.b),确保每次推导出新状态
- 数组方式也可行:new long[]{0L, 1L} → new long[]{pair[1], pair[0] + pair[1]}
peek 必须放在 map 提取之后才能看到目标值
iterate 产出的是状态对象(如 Fib 或 long[]),不是你要的斐波那契数字。若直接在 iterate 后加 peek,打印出来的是 Fib{a=0, b=1} 这类结构,毫无监控价值。
- 正确链路:iterate(...) → map(f -> f.a) → peek(n -> log("current=", n))
- map 必须在 peek 前,否则 peek 拿到的是未解包的状态,不是业务关心的变量
- limit() 或 takeWhile() 放在 peek 之后,避免无限生成导致 OOM
用 peek 记录多维度变量快照
单打一打印数字不够诊断。可在 peek 中构造轻量日志,包含阶段标识、序号、时间戳和关键字段:
- 加计数器(AtomicInteger)记录处理序号,避免依赖 forEachIndex
- 用 Instant.now() 替代 new Date(),精度更高且线程安全
- 记录当前项 f.a、下一项 f.b、以及它们的和 f.a + f.b,便于验证演进是否正确
- 高吞吐场景下,用 counter.getAndIncrement() % 100 == 0 实现 1% 采样,防日志刷屏
避开并行与副作用陷阱
iterate 生成的流默认串行,但若后续转 parallelStream(),peek 里的日志输出线程就不可控。更危险的是在 peek 里做修改操作:
- 禁止在 peek 中修改状态对象(如 f.setA(...)),这破坏不可变契约,可能引发排序错乱或结果不一致
- 不要同步写文件或发 HTTP 请求——阻塞会拖慢整个流,违背“不影响主业务流”前提
- 日志统一走异步 Appender(如 Logback AsyncAppender),MDC 注入 traceId 实现链路追踪
- 想看原始状态?把 peek 放在 map 前;想看转换后?放在 map 后——位置决定你看到哪一“帧”











