Melon Playground甜瓜游乐场
98.73M · 2026-03-22
如果你已经在用 Java 8 的 Stream API,可能已经习惯了 .filter()、.map()、.collect() 这些基础操作。它们让代码变得更简洁、更具声明式。
但真正考验功力的,是当你遇到更复杂的场景时:嵌套集合如何优雅展平?求和运算 reduce 和 collect 该选哪个?并行流 parallelStream 到底是性能神器还是并发炸弹?
这篇文章不讲 API 文档,而是从实战出发,复盘这三个高级特性的核心原理、典型场景以及那些容易踩的坑。
flatMap 是 Stream API 中最容易被误解,却又最强大的中间操作之一。
用一句话概括它的本质:将流中的每个元素映射为一个子流,然后将所有子流"压平"为一个统一的流。
看方法签名:
java
<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper)
TR 的 StreamStream<R>,而非 Stream<Stream<R>>这是 flatMap 的招牌场景——处理"集合的集合"。
java
List<List<String>> nestedList = Arrays.asList(
Arrays.asList("Java", "Stream"),
Arrays.asList("flatMap", "map"),
Arrays.asList("集合", "展平")
);
// 错误示范:map 会得到 Stream<Stream<String>>
List<Stream<String>> mapResult = nestedList.stream()
.map(list -> list.stream())
.collect(Collectors.toList());
// 正确示范:flatMap 展平为单层 Stream
List<String> flatMapResult = nestedList.stream()
.flatMap(list -> list.stream())
.collect(Collectors.toList());
// 输出:[Java, Stream, flatMap, map, 集合, 展平]
理解关键:map 是"一对一"映射,flatMap 是"一对多"映射后再合并。
java
// 避免嵌套的 Optional<Optional<T>>
Optional<String> city = user.getAddress()
.flatMap(Address::getCity);
java
// 将多个文件的所有行合并为一个流
Stream<String> allLines = filePaths.stream()
.flatMap(path -> {
try {
return Files.lines(Paths.get(path));
} catch (IOException e) {
return Stream.empty();
}
});
很多开发者会混淆 reduce 和 collect,觉得它们都能"把流变成一个结果"。但它们的设计语义完全不同。
reduce 的核心语义是折叠:将流中所有元素通过累积函数逐步聚合,最终生成一个值。
它有三种重载形式,覆盖不同场景:
java
Optional<Integer> sum = Arrays.asList(1, 2, 3, 4)
.stream()
.reduce((a, b) -> a + b);
适用于:流可能为空,需要避免 NPE 的场景。
java
int sumWithInit = Arrays.asList(1, 2, 3, 4)
.stream()
.reduce(0, (a, b) -> a + b);
适用于:需要明确默认值,且结果类型与元素类型一致。
java
String result = Arrays.asList(1, 2, 3, 4)
.stream()
.parallel()
.reduce("",
(acc, num) -> acc + num + ",", // 累积函数
(acc1, acc2) -> acc1 + acc2); // 组合器:合并并行结果
collect 的核心语义是填充:将流元素收集到容器或构建复杂对象。
java
List<String> list = stream.collect(Collectors.toList());
Map<Integer, List<String>> grouped = stream.collect(Collectors.groupingBy(String::length));
| 维度 | reduce(归约) | collect(收集) |
|---|---|---|
| 核心语义 | 聚合为单个值(折叠) | 收集到容器/复杂对象(填充) |
| 结果类型 | 单个值(Integer、String) | 容器/复杂对象(List、Map、POJO) |
| 操作方式 | 不可变操作(每次生成新值) | 可变操作(直接修改容器) |
| 并行流 | 需显式指定 combiner | 框架自动处理 |
| 典型场景 | 求和、求最值、字符串拼接 | 转集合、分组、构建复杂对象 |
java
Optional<List<String>> result = stream
.reduce(new ArrayList<>(),
(list, str) -> {
List<String> newList = new ArrayList<>(list);
newList.add(str);
return newList;
},
(list1, list2) -> {
List<String> newList = new ArrayList<>(list1);
newList.addAll(list2);
return newList;
});
每次操作都创建新 List,性能极差。
java
List<String> result = stream.collect(Collectors.toList());
直接修改可变容器,性能最优。
并行流 parallelStream() 是 Java 8 为 Stream API 提供的并行处理能力,基于 Fork/Join 框架。但它不是"万能药",用错了反而会更慢。
plaintext
数据源 → Fork(拆分任务) → 多线程并行处理 → Join(合并结果)
ForkJoinPool.commonPool() 中执行| 条件 | 说明 |
|---|---|
| 数据量足够大 | 元素数 ≥ 1000,否则并行开销会抵消收益 |
| 计算密集型 | 每个元素处理耗时(复杂计算、转换),而非简单遍历 |
| 无状态操作 | 不依赖外部状态、不修改共享变量(纯函数) |
| 数据源支持拆分 | ArrayList 支持,LinkedList 不支持 |
java
// 10万条数据的质数判断(计算密集型)
List<Integer> primes = largeList.parallelStream()
.filter(ParallelStreamSuitable::isPrime)
.collect(Collectors.toList());
LinkedList(拆分成本高)这是并行流最容易踩的坑——多线程同时修改共享变量会导致数据竞争。
java
private static int count = 0; // 共享可变状态
list.parallelStream().forEach(num -> {
count++; // 多线程同时修改,结果不可预期
});
System.out.println(count); // 可能输出 8、9,而不是预期的 10
问题根源:
count++ 不是原子操作(读取→加1→写入)方案一:使用 reduce(推荐)
java
long count = list.parallelStream()
.reduce(0L, (acc, num) -> acc + 1, Long::sum);
无共享状态,纯函数操作,线程安全。
方案二:使用原子类(兜底)
java
AtomicInteger atomicCount = new AtomicInteger(0);
list.parallelStream().forEach(num -> {
atomicCount.incrementAndGet();
});
java
List<Integer> result = new ArrayList<>(); // 非线程安全容器
list.parallelStream().forEach(result::add); // 可能抛异常或丢失元素
正确方案:使用 collect
java
List<Integer> result = list.parallelStream()
.collect(ArrayList::new, ArrayList::add, ArrayList::addAll);
ForkJoinPool.commonPool(),核心线程数 = CPU 核心数 - 1forEach 不保证顺序,顺序处理用 forEachOrdered(会损失性能)写代码容易,写出优雅、高效的代码需要思考。 下次用 Stream 时,不妨多问自己一句:这个操作真的需要并行吗?reduce 和 collect 哪个更合适?
参考:Java 8 Stream API 官方文档