必须调用终端操作才能激活Stream中间操作,如collect()、forEach()等;peek仅在终端操作执行时触发,且受其类型影响;避免expensive操作提前执行需用Supplier封装;Stream不可复用,sorted应后置,filter宜优先于map以减少计算。

要让 Stream 中间操作真正执行,关键不是“避免惰性求值”,而是**确保调用终端操作**。惰性求值是 Stream 的设计核心,并非缺陷;所谓“未执行”,本质是漏掉了触发执行的那一步。
必须调用终端操作才能激活流水线
filter、map、peek 等中间操作只组装指令链,不处理数据。就像写好剧本但没开拍——没有终端操作,整个流就静止不动。
- 常见终端操作有:collect()、forEach()、count()、findFirst()、anyMatch()、reduce()
- 错误写法(无输出、无效果):list.stream().filter(x -> x > 0).map(String::valueOf);
- 正确写法(加上终端操作):list.stream().filter(x -> x > 0).map(String::valueOf).collect(Collectors.toList());
调试时 peek 没打印?先确认终端操作是否被调用
peek 是中间操作,常被误当“立刻执行的日志工具”。它只在流水线实际运行时触发,且受终端操作类型影响:
Java项目代码review工具。分析Git变更+完整调用链路上下文,推断业务需求,进行多维度评分和分类汇总,生成完整PRD文档。包含细粒度Java代码审查清单(Null安全、异常处理、Streams、并发、equals/hashCode、资源管理、API设计、性能、MyBatis/ORM、事务边界、SQL/DD...
- 短路终端操作(如 findFirst、anyMatch)可能只处理前几个元素,peek 也就只打印那么几次
- 没加终端操作,peek 完全不会执行——控制台必然空着
- 并行流中 peek 输出顺序不可靠,不建议用于断言或逻辑判断
高开销方法被提前执行?问题出在 Supplier 少了一层封装
若想“缓存未命中才查数据库”,不能直接把 expensiveList() 放进 stream.concat(),否则它会在构建阶段就调用。
立即学习“Java免费学习笔记(深入)”;
- ❌ 错误:Stream.concat(stream1, expensiveList().stream()) → expensiveList() 立即执行
- ✅ 正确:Stream.concat(stream1, Stream.generate(() -> expensiveList()).flatMap(List::stream)) → expensiveList() 延迟到 flatMap 消费时才调用
- 更清晰写法:用 Supplier
- > 封装昂贵操作,再在 flatMap 中 get()
其他典型陷阱与应对
- Stream 复用失败:同一个 Stream 实例只能消费一次,重复调用 forEach 会抛 IllegalStateException。需重新获取流,如 list.stream()
- sorted 放太前:sorted 是有状态操作,会强制缓冲全部上游数据。若上游是大数据源或无限流,应尽量后置或避免
- filter 和 map 顺序不合理:先 map 再 filter 可能对所有元素都执行了昂贵转换;优先 filter 再 map,可减少无效计算

















