Java 8 Stream 是一次性数据处理管道,非集合替代品;误用会导致异常,需避免null、副作用,合理使用collect和groupingBy。

Java 8 的 Stream 不是集合,也不能替代集合;它是一次性、不可重用的数据处理管道,用错场景或误用终端操作会导致 IllegalStateException 或空指针。
Stream 创建时别直接用 null 集合调 stream()
常见错误:从 DAO 层拿到可能为 null 的 List,直接链式调用 .stream().filter(...),结果抛 NullPointerException。
- 安全写法:先判空,或用
Optional.ofNullable(list).orElse(Collections.emptyList()).stream() - 更简洁:用
Objects.requireNonNullElse(list, Collections.emptyList()).stream()(JDK 9+);JDK 8 就老实用三元运算符 - 注意
Arrays.asList(null)会创建含一个null元素的列表,不是空流 —— 这和Stream.of(null)效果一致,但和Stream.empty()完全不同
filter() 和 map() 中别做有副作用的操作
Stream 设计原则是无状态、无副作用;在 filter 里改外部变量、打印日志、发 HTTP 请求,看似能跑通,但并行流(parallelStream())下行为不可预测,且违反函数式语义。
- 正确做法:过滤逻辑只返回
boolean,转换逻辑只返回新对象;副作用移出 Stream 链,在forEach或收集后处理 - 例如:不要写
list.stream().filter(x -> { log.info(x); return x > 5; }),应拆成list.stream().filter(x -> x > 5).peek(x -> log.info(x))(peek是专为调试设计的“合法副作用”入口) -
peek()不是终端操作,必须接collect()或forEach()才会触发
收集结果时小心 Collectors.toList() 和 Collectors.toSet() 的陷阱
toList() 返回的是不可变视图(JDK 16+ 是 ImmutableCollections.ListN),但 JDK 8 实际返回的是 ArrayList —— 可修改;而 toSet() 不保证顺序,且元素必须正确实现 hashCode()/equals()。
立即学习“Java免费学习笔记(深入)”;
- 需要线程安全可变列表?用
Collectors.toCollection(ArrayList::new) - 要保持插入顺序的去重?别用
toSet(),改用collect(Collectors.collectingAndThen(Collectors.toCollection(LinkedHashSet::new), LinkedHashSet::new)),或更直白:先distinct()再collect(toList()) -
Collectors.groupingBy(keyMapper)默认用HashMap,如果 key 是枚举或需有序,显式传TreeMap::new作为下游 map factory
Map<String, List<Person>> grouped = people.stream()
.collect(Collectors.groupingBy(
Person::getDepartment,
TreeMap::new,
Collectors.toList()
));
Stream 的真正难点不在语法,而在思维切换:它不关心“怎么循环”,只声明“要什么结果”。一旦开始在 lambda 里写 for 或缓存中间状态,就说明该停下来重想数据流了。
















