一、核心定位:把「嵌套结构」拆成「平铺结构」
flatMap的本质是先映射,再扁平化:
- 对每个元素做映射(比如把一个字符串拆成字符数组)
- 把所有映射后的子流合并成一个顶级流(把多个字符数组合并成一个字符流)
生活类比:把一整箱苹果(每个苹果是一个独立的小包装)拆开,把所有苹果倒在一个大篮子里;把一本本的书拆开,把所有页码铺在桌面上。
二、为什么需要flatMap?对比map的局限性
先看一个反例,用map处理嵌套结构会出现什么问题:
// 需求:把["Hello", "World"]拆成单个字符的列表
String[] words = {"Hello", "World"};
// 用map处理:返回的是Stream<String[]>,每个元素是一个字符数组
List<String[]> wrongResult = Arrays.stream(words)
.map(word -> word.split(""))
.collect(Collectors.toList());
// 输出:[[Ljava.lang.String;@12edcd21, [Ljava.lang.String;@34c45dca]
System.out.println(wrongResult);
问题:map是「一对一」映射,每个单词映射成一个字符数组,最终得到的是「数组的列表」,而不是「字符的列表」。
解决方案:用flatMap把嵌套的流扁平化:
// 需求:把["Hello", "World"]拆成单个字符的列表
String[] words = {"Hello", "World"};
// 用map处理:返回的是Stream<String[]>,每个元素是一个字符数组
List<String[]> wrongResult = Arrays.stream(words)
.map(word -> word.split(""))
.collect(Collectors.toList());
// 输出:[[Ljava.lang.String;@12edcd21, [Ljava.lang.String;@34c45dca]
System.out.println(wrongResult);
map vs flatMap 核心对比表
| 特性 | map | flatMap |
|---|---|---|
| 映射关系 | 一对一(一个元素→一个结果) | 一对多(一个元素→多个结果) |
| 返回类型 | Stream | Stream |
| 处理嵌套结构 | 会生成嵌套流(Stream) | 会扁平化为单层流(Stream) |
| 核心逻辑 | 只做映射,不做合并 | 先映射,再合并所有子流 |
三、flatMap的核心用法场景
1. 处理嵌套集合(最常用)
把「列表的列表」转换成「单层列表」:
List<List<Integer>> nestedList = Arrays.asList(
Arrays.asList(1, 2, 3),
Arrays.asList(4, 5, 6),
Arrays.asList(7, 8, 9)
);
// 扁平化处理:把List<List<Integer>>变成List<Integer>
List<Integer> flatList = nestedList.stream()
.flatMap(List::stream) // 把每个子列表转成流并合并
.collect(Collectors.toList());
// 输出:[1, 2, 3, 4, 5, 6, 7, 8, 9]
System.out.println(flatList);
2. 处理对象中的集合属性
比如每个用户有多个订单,把所有用户的订单合并成一个订单流:
class User {
private String name;
private List<Order> orders;
// 构造方法、getter省略
}
class Order {
private String orderId;
private double amount;
// 构造方法、getter省略
}
// 需求:统计所有用户的订单总金额
List<User> users = Arrays.asList(
new User("张三", Arrays.asList(new Order("ORD001", 100), new Order("ORD002", 200))),
new User("李四", Arrays.asList(new Order("ORD003", 150), new Order("ORD004", 250)))
);
double totalAmount = users.stream()
.flatMap(user -> user.getOrders().stream()) // 把每个用户的订单转成流
.mapToDouble(Order::getAmount) // 提取订单金额
.sum(); // 求和
// 输出:700.0
System.out.println(totalAmount);
3. 处理多层嵌套结构
如果是三层甚至更多层嵌套,需要多次调用flatMap:
// 三层嵌套:List<List<List<Integer>>>
List<List<List<Integer>>> tripleNested = Arrays.asList(
Arrays.asList(Arrays.asList(1, 2), Arrays.asList(3, 4)),
Arrays.asList(Arrays.asList(5, 6), Arrays.asList(7, 8))
);
// 三层扁平化
List<Integer> tripleFlat = tripleNested.stream()
.flatMap(List::stream) // 第一层:List<List<List>> → Stream<List<List>>
.flatMap(List::stream) // 第二层:Stream<List<List>> → Stream<List>
.flatMap(List::stream) // 第三层:Stream<List> → Stream<Integer>
.collect(Collectors.toList());
// 输出:[1, 2, 3, 4, 5, 6, 7, 8]
System.out.println(tripleFlat);
4. 基本数据类型的扁平化
Java 8提供了专门的方法处理基本数据类型的流,避免自动装箱拆箱的性能损耗:
| 方法名 | 作用 | 返回类型 |
|---|---|---|
flatMapToInt | 扁平化为IntStream | IntStream |
flatMapToLong | 扁平化为LongStream | LongStream |
flatMapToDouble | 扁平化为DoubleStream | DoubleStream |
示例:统计所有数字列表的总和
List<List<Integer>> numberLists = Arrays.asList(
Arrays.asList(1, 2, 3),
Arrays.asList(4, 5, 6)
);
int sum = numberLists.stream()
.flatMapToInt(list -> list.stream().mapToInt(Integer::intValue))
.sum();
// 输出:21
System.out.println(sum);
四、核心知识点记忆清单
我把flatMap的关键规则整理成5条记忆口诀,看完就能记住:
1. 「先映射,再合并,嵌套结构变单层」
flatMap的逻辑永远是:先对每个元素做映射(生成子流),再把所有子流合并成一个顶级流。
2. 「map一对一,flatMap一对多」
- map:一个元素对应一个结果,适合简单的类型转换
- flatMap:一个元素对应多个结果,适合拆分、展开嵌套结构
3. 「返回必须是流,不能直接返集合」
flatMap的Lambda表达式必须返回Stream类型,不能直接返回List或数组,否则会编译错误:
// 错误写法:直接返回List,编译报错
listOfLists.stream().flatMap(list -> list);
// 正确写法:把List转成Stream
listOfLists.stream().flatMap(list -> list.stream());
4. 「多层嵌套多次平,一层一次flatMap」
如果是N层嵌套,就需要调用N次flatMap,每次展开一层。
5. 「空集合要处理,避免空指针」
如果嵌套的集合可能为null,需要先过滤或用Optional处理,避免NullPointerException:
// 安全写法:先过滤null,再转成流
users.stream()
.map(User::getOrders)
.filter(Objects::nonNull) // 过滤null集合
.flatMap(List::stream)
.collect(Collectors.toList());
五、常见误区避坑
- map和flatMap顺序反了:必须先map生成子流,再flatMap合并,顺序不能反
- 直接返回集合而不是流:flatMap要求返回Stream,不能直接返回List或数组
- 忽略空集合:嵌套集合为null时,调用
list.stream()会抛出空指针异常 - 并行流的副作用:flatMap是懒求值的,并行流中lambda的副作用(比如打印日志)会在流消费时才触发,且顺序不确定
六、记忆口诀总结
flatMap扁平化,先映射后合并;
map一对一,flatMap一对多;
嵌套结构变单层,返回必须是Stream;
多层嵌套多次平,空集合要先过滤。
10万+

被折叠的 条评论
为什么被折叠?



