flatMap 深入:一对多场景利器(AI 辅助学习 Java 8)
上周学了 Collectors 全家桶。今天深入 Stream 中最容易被误解的操作——flatMap。掌握它,就能轻松处理嵌套集合、标签提取、一对多关联等复杂场景。
一、map vs flatMap 的本质区别
一句话:map 是一对一,flatMap 是一对多再展平。
List<List<String>> nested = Arrays.asList(
Arrays.asList("Java", "Lambda"),
Arrays.asList("Vue", "React"),
Arrays.asList("Docker", "K8s")
);
// map:每个 List 转为 Stream,结果是 Stream<Stream<String>>
nested.stream().map(List::stream)
// 3 个独立的 Stream
// flatMap:每个 List 转为 Stream,然后展平为一个 Stream<String>
nested.stream().flatMap(List::stream)
// 一个 Stream,包含所有元素: ["Java", "Lambda", "Vue", "React", "Docker", "K8s"]
💡 AI 类比:问 AI —— "map 和 flatMap 用前端概念怎么理解?"
AI 会告诉你:
map类似Array.map,每个元素一对一转换flatMap类似Array.flatMap,先map成数组,再flat(1)展平一层
二、一对多场景:提取所有标签
每篇文章有多个标签,要获取所有不重复的标签:
Set<String> uniqueTags = articles.stream()
.flatMap(article -> article.getTags().stream())
.collect(Collectors.toSet());
// ["Java", "Lambda", "Vue", "React", "Docker", ...]
演变过程:
// 第一步:map 得到 Stream<List<String>>
articles.stream().map(Article::getTags)
// Stream<List<String>> —— 每个元素是一个标签列表
// 第二步:flatMap 展平为 Stream<String>
articles.stream().flatMap(a -> a.getTags().stream())
// Stream<String> —— 所有标签在一个流中
标签统计:
Map<String, Long> tagCount = articles.stream()
.flatMap(Article::getTagsStream)
.collect(Collectors.groupingBy(tag -> tag, Collectors.counting()));
// {Java=2, 前端=2, Vue=1, React=1, ...}
三、嵌套集合处理:反向索引
按标签反向查找:给定一个标签,找出所有包含该标签的文章。
Map<String, List<String>> tagToArticles = articles.stream()
.flatMap(article -> article.getTags().stream()
.map(tag -> new AbstractMap.SimpleEntry<>(tag, article.getTitle())))
.collect(Collectors.groupingBy(
Map.Entry::getKey,
Collectors.mapping(Map.Entry::getValue, Collectors.toList())
));
// Java: [Lambda 入门, Stream 详解]
// Vue: [Vue3 API]
// 前端: [Vue3 API, React Hooks]
这个操作的核心是 flatMap 内部的 map:每篇文章展开为多个 (标签, 标题) 对,然后按标签分组。
四、flatMapToInt:转基本类型流
当需要将嵌套集合中的数值聚合时,用 flatMapToInt 避免装箱拆箱开销。
// 所有关键词的总字符数
int totalChars = articles.stream()
.flatMapToInt(a -> a.getKeywords().stream().mapToInt(String::length))
.sum();
对应的还有 flatMapToLong 和 flatMapToDouble。
五、运行结果
=== map vs flatMap ===
map: 3 个 Stream
flatMap: [Java, Lambda, Vue, React, Docker, K8s]
=== 提取所有标签 ===
所有标签(去重): [Java, Vue, React, Docker, ...]
标签统计: {Java=2, 前端=2, Vue=1, React=1, ...}
最热门标签: Java
=== 嵌套集合处理 ===
标签->文章:
Java: [Lambda 入门, Stream 详解]
前端: [Vue3 API, React Hooks]
Docker: [Docker 指南]
=== flatMapTo 基本类型流 ===
所有关键词总字符数: 58
所有阅读量: [200, 350, 500, 400, 250]
六、flatMap 常见场景速查
| 场景 | flatMap 用法 |
|---|---|
| 嵌套 List 展平 | .flatMap(List::stream) |
| 一对多提取 | .flatMap(obj -> obj.getCollection().stream()) |
| 字符串拆分为字符流 | .flatMap(s -> Arrays.stream(s.split(""))) |
| Optional 展平 | .flatMap(obj -> obj.getOptional().stream()) |
| 转 IntStream | .flatMapToInt(obj -> obj.getInts().stream().mapToInt(i -> i)) |
七、下篇预告
周六我们将用 Stream + Collectors + flatMap 实现博客数据的完整统计:求和、平均值、分组汇总、标签云等实战场景。
系列文章导航:
更多请关注我的微信公众号,感谢!