在Java的Stream流式处理体系中,flatMap是一个用于将嵌套数据结构展开的核心方法,它可以把多个流合并成一个流,解决多层嵌套集合的处理难题。相比传统的多层for循环,使用flatMap可以让代码更简洁,逻辑更清晰。

flatMap的核心作用
flatMap的底层逻辑是先对原流中的每个元素执行一个返回流的函数,再把所有得到的流拼接成一个新的流,也就是完成“映射+扁平化”两个操作。它的典型使用场景包括:
- 展开嵌套的集合结构,比如<code>List<List<String>></code>转成<code>List<String></code>
- 处理对象中包含集合属性的场景,提取所有对象的集合元素
- 拆分字符串等场景,将单个元素转换成多个元素后合并
基础用法:展开嵌套列表
最常见的场景是处理嵌套的列表结构,比如有一个包含多个子列表的主列表,需要把所有子列表的元素合并到一个新列表中。
传统循环的实现方式如下:
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class FlatMapDemo {
public static void main(String[] args) {
// 嵌套列表结构
List<List<String>> nestedList = Arrays.asList(
Arrays.asList("a", "b", "c"),
Arrays.asList("d", "e"),
Arrays.asList("f", "g", "h")
);
// 传统循环展开
List<String> resultByLoop = new ArrayList<>();
for (List<String> subList : nestedList) {
resultByLoop.addAll(subList);
}
System.out.println(resultByLoop);
}
}
使用flatMap的实现方式如下:
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class FlatMapDemo {
public static void main(String[] args) {
List<List<String>> nestedList = Arrays.asList(
Arrays.asList("a", "b", "c"),
Arrays.asList("d", "e"),
Arrays.asList("f", "g", "h")
);
// 使用flatMap展开
List<String> resultByFlatMap = nestedList.stream()
.flatMap(subList -> subList.stream())
.collect(Collectors.toList());
System.out.println(resultByFlatMap);
}
}
两种方式最终输出的结果都是[a, b, c, d, e, f, g, h],但flatMap的实现只需要一行核心逻辑,没有显式的循环嵌套。
进阶用法:处理对象嵌套集合的场景
实际开发中更常见的是对象包含集合属性的场景,比如有一个用户类,每个用户有多个爱好,需要提取所有用户的爱好组成一个新的集合。
首先定义用户类:
import java.util.List;
class User {
private String name;
private List<String> hobbies;
public User(String name, List<String> hobbies) {
this.name = name;
this.hobbies = hobbies;
}
public List<String> getHobbies() {
return hobbies;
}
}
使用flatMap提取所有爱好的实现如下:
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class FlatMapDemo {
public static void main(String[] args) {
List<User> userList = Arrays.asList(
new User("张三", Arrays.asList("篮球", "足球")),
new User("李四", Arrays.asList("跑步", "游泳", "骑行")),
new User("王五", Arrays.asList("绘画"))
);
// 提取所有用户的爱好
List<String> allHobbies = userList.stream()
.flatMap(user -> user.getHobbies().stream())
.collect(Collectors.toList());
System.out.println(allHobbies);
}
}
运行后输出的结果是[篮球, 足球, 跑步, 游泳, 骑行, 绘画],flatMap自动把每个用户的爱好集合展开,合并成了最终的流。
其他常见使用场景
拆分字符串并展开
如果需要把一个句子拆分成单词,再处理所有单词,也可以使用flatMap:
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class FlatMapDemo {
public static void main(String[] args) {
List<String> sentences = Arrays.asList("hello world", "java stream flatMap", "test demo");
// 拆分句子为单词并展开
List<String> allWords = sentences.stream()
.flatMap(sentence -> Arrays.stream(sentence.split(" ")))
.collect(Collectors.toList());
System.out.println(allWords);
}
}
输出结果为[hello, world, java, stream, flatMap, test, demo]。
过滤空值后展开
如果嵌套结构中可能存在空集合,可以结合filter先过滤空值,再展开:
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class FlatMapDemo {
public static void main(String[] args) {
List<List<String>> nestedListWithNull = Arrays.asList(
Arrays.asList("a", "b"),
null,
Arrays.asList("c", "d"),
Arrays.asList()
);
List<String> result = nestedListWithNull.stream()
.filter(subList -> subList != null && !subList.isEmpty())
.flatMap(subList -> subList.stream())
.collect(Collectors.toList());
System.out.println(result);
}
}
输出结果为[a, b, c, d],避免了空指针异常和空集合的干扰。
flatMap与map的区别
很多开发者会混淆map和flatMap的用法,两者的核心区别如下:
| 方法 | 作用 | 返回值类型 |
|---|---|---|
| map | 对每个元素进行转换,返回的是转换后的元素组成的流 | Stream<R>,R是转换后的元素类型 |
| flatMap | 对每个元素进行转换,转换结果是一个流,再把这些流合并成一个流 | Stream<R>,R是流中元素的类型,最终流是扁平化的 |
简单来说,map是一对一转换,flatMap是一对多转换并合并。比如<code>List<List<String>></code>用map处理的话,得到的是<code>Stream<List<String>></code>,而用flatMap处理得到的是<code>Stream<String></code>。
使用注意事项
- flatMap的函数参数必须返回一个流,否则会编译报错
- 如果原流中有null元素,直接调用flatMap可能会抛出空指针异常,建议先过滤null
- flatMap是中间操作,需要结合终止操作才会执行,比如collect、forEach等
- 展开后的流是无序的,如果需要保持原有顺序,不需要额外处理,Stream的有序性在flatMap后默认会保留,除非使用了并行流且原流是无序的