Stream本质是单次、惰性执行的数据管道:中间操作仅登记任务,终端操作才触发执行;一个Stream只能消费一次;需分清中间操作(返回新Stream)与终端操作(终结流并产出结果)的职责,避免链式调用错误。

Stream API 复习不靠死记,关键在理清“数据流怎么走、操作怎么串、哪里会断”。重点抓三条主线:流的生命周期、中间与终端操作的协作逻辑、以及常见误用场景。
抓住流的“一次性”和“惰性执行”本质
这是所有问题的根源。Stream 不是容器,它是一条只通一次的管道:
- 创建后不执行任何计算,filter、map、sorted 等中间操作只是登记任务,不触发处理
- 只有遇到 collect、forEach、count、reduce 这类终端操作时,整条流水线才从头开始执行
- 一个 Stream 只能被消费一次——调完 collect 再 forEach,必然抛 IllegalStateException
- 如果需要多次使用,要么重新创建流(
list.stream()),要么把结果存为集合再复用
分清中间操作和终端操作的职责边界
别混淆“搭管道”和“开闸放水”:
- 中间操作返回新 Stream:filter、map、flatMap、sorted、distinct、limit、skip、peek —— 它们可无限链式调用,但不改变原数据,也不触发执行
- 终端操作终结流并产出结果:collect(最常用)、forEach(带副作用)、count、anyMatch、findFirst、reduce —— 执行后流即关闭,不能再参与后续链式调用
- 典型错误:写成
stream.filter(...).map(...).forEach(...).collect(...)—— forEach 是终端操作,后面 collect 已无效
熟练掌握 Collectors 的高频组合
collect 是 Stream 的“出口”,用不好就白搭:
立即学习“Java免费学习笔记(深入)”;
-
基础收集:
Collectors.toList()、toSet()、toCollection(LinkedList::new) -
分组统计:
Collectors.groupingBy(User::getDepartment)→ 按部门分组;加Collectors.counting()可统计人数 -
分区布尔条件:
Collectors.partitioningBy(u -> u.getAge() >= 18)→ 自动分 true/false 两组 -
聚合计算:
Collectors.summingInt(User::getSalary)、averagingDouble、maxBy - 注意:
toMap容易因 key 冲突报错,记得配(old, new) -> new解决重复键
警惕三个高频踩坑点
这些不是语法错误,而是逻辑陷阱:
- 并行流不是万能加速器:小数据量用 parallelStream 反而更慢;有状态操作(如 peek 修改外部变量)或非线程安全收集器(如 ArrayList)会导致结果不可靠
- flatMap 误用成 map:想压平嵌套列表(List<List<String>> → List<String>)必须用 flatMap;若用 map,结果是 Stream<List<String>>,不是你想要的扁平结构
-
空指针没被 filter 拦住:filter 传入的 lambda 若本身抛 NPE(如
e.getName().length() > 5而 e 为 null),不会被 filter 拦截——得先用Objects.nonNull(e)或 Optional 处理


















