Java Stream API:函数式数据处理管道
更新时间:2026-09-21。Lambda 与函数式接口是 Stream 的前置,见 Lambda 与函数式接口;空值处理见 Optional。
一、Stream 不是集合
Stream 是对数据源的一次性流式计算声明,关键三点:
- 不存储数据:数据仍在背后的集合/数组/生成器里。
- 惰性求值:中间操作(filter/map…)不立即执行,遇到终端操作才真正跑。
- 只能消费一次:终端操作后流即关闭,再用抛
IllegalStateException。
java
var s = List.of(1,2,3).stream().filter(x -> x > 1);
s.count(); // 2
s.count(); // IllegalStateException:stream has already been operated upon二、创建流
java
list.stream();
Arrays.stream(arr);
Stream.of("a","b");
map.entrySet().stream();
Files.lines(Path.of("a.txt")); // IO 流,注意 try-with-resources
Stream.iterate(0, i -> i+1).limit(10);
Stream.generate(Math::random).limit(5);三、常用中间操作(lazy,返回新 Stream)
| 操作 | 作用 |
|---|---|
filter | 过滤 |
map | 一对一转换 |
flatMap | 一对多摊平(List<List<T>>→单个元素流) |
distinct | 按 equals 去重 |
sorted()/sorted(cmp) | 排序,比较器见 Comparable/Comparator |
limit(n)/skip(n) | 截断/跳过 |
peek | 调试偷看,无终端操作时不执行 |
flatMap 把每个元素映射成一个流再摊平,最容易卡住:
java
var groups = List.of(List.of(1,2), List.of(3,4));
var flat = groups.stream().flatMap(List::stream).toList(); // [1,2,3,4]四、终端操作(触发计算)
| 操作 | 返回 | 说明 |
|---|---|---|
forEach | void | 遍历,并行下顺序不保证 |
collect(toList()) | List | JDK16 起可直接 .toList()(不可变) |
count/min/max | long/Optional | |
anyMatch/allMatch/noneMatch | boolean | 短路 |
findFirst/findAny | Optional | 短路 |
reduce | 归约值 | 累加/求和的通用形式 |
java
int sum = List.of(1,2,3).stream().reduce(0, Integer::sum);五、Collectors:分组、分区、拼接
java
Map<Dept, List<Emp>> byDept = emps.stream().collect(groupingBy(Emp::dept));
Map<Dept, Long> cnt = emps.stream().collect(groupingBy(Emp::dept, counting()));
Map<Boolean, List<Integer>> part = nums.stream().collect(partitioningBy(n -> n%2==0));
String csv = ids.stream().map(String::valueOf).collect(joining(","));六、常见坑
- peek 里塞业务逻辑:peek 只用于调试,没有终端操作时整段流都不执行。
- lambda 里修改外部集合/变量:破坏无状态约定,并行时结果错乱,副作用用 collect/reduce 表达。
- 复用同一个 Stream:一次终端操作即关闭,需要多次就重新创建。
- 无脑 parallelStream:底层共享 ForkJoinPool,小数据量、有 IO、非线程安全收集器下更慢或出错。
.toList()返回不可变 List(JDK16+):要可变列表用collect(toCollection(ArrayList::new))。- 无限流不加 limit:
iterate/generate必须配合 limit 或短路操作。
总结
Stream 是对数据源的一次性惰性计算管道,filter/map/flatMap/sorted/distinct 等中间操作只在 forEach/collect/count/reduce/match 等终端操作触发时才执行、且一个流只能消费一次;一对多摊平用 flatMap,分组用 groupingBy、布尔二分用 partitioningBy、拼接用 joining,JDK16 可直接 toList(返回不可变);peek 仅供调试、lambda 里不要修改外部状态、parallelStream 不要无脑开,归约优先用 collect/reduce 而非副作用累加。