map用于一对一转换,保持元素数量不变;flatMap用于一对多映射并扁平化,将多个子流合并为单一流,避免嵌套结构。

map 和 flatMap 都是 Java 8 Stream 的核心中间操作,但它们解决的问题完全不同:map 做的是“一对一”元素转换,而 flatMap 做的是“一对多 + 扁平化”处理。选错方法,轻则结果嵌套混乱,重则编译失败或运行时异常。
map:保持结构,只换内容
map 接收一个 Function<T, R>,对每个元素执行一次转换,输出流中元素个数与输入完全一致。它不碰结构层级,适合干净利落的字段提取、格式调整或类型转换。
- 把字符串列表转成大写:
.map(String::toUpperCase) - 从用户对象中提取姓名:
.map(User::getName) - 计算每个字符串长度:
.map(String::length) - 注意:若函数返回 null,map 会原样保留 null 元素(不会跳过)
flatMap:拆开再合并,专治嵌套
flatMap 接收一个 Function<T, Stream<R>>,先为每个元素生成一个流,再把所有子流“压平”成一个单一的流。它真正的作用不是映射,而是解构 + 合并。
- 把句子列表拆成单词流:
.flatMap(s -> Arrays.stream(s.split(" "))) - 展开嵌套列表
List<List<String>>:.flatMap(List::stream) - 将字符串转为字符流:
.flatMap(s -> s.chars().mapToObj(c -> (char) c)) - 关键点:如果某个元素映射出空流(如
Stream.empty()),它就不出现在最终结果里
误用后果很直观
用 map 处理本该用 flatMap 的场景,最典型的表现是类型不匹配或结果“多一层”:
立即学习“Java免费学习笔记(深入)”;
- 想把
List<List<Integer>>展平却用了 map → 得到Stream<List<Integer>>,无法直接 collect 成List<Integer> - 想拆分字符串却用 map → 返回
Stream<String[]>,而不是你想要的单个单词流 - 遇到 Optional 时也同理:map 把值包进新 Optional,flatMap 要求你手动返回 Optional,避免
Optional<Optional<T>>
怎么一眼判断该用哪个?
看你的转换函数返回什么:
- 返回一个普通对象(
String、Integer、UserDTO)→ 用 map - 返回一个流(
Stream<T>)、集合(需转流)、数组(需用Arrays.stream())→ 用 flatMap - 不确定?写完后看 IDE 提示的泛型类型——如果出现
Stream<Stream<...>>,立刻换 flatMap


















