我们来详细解释一下 Java 8 Stream API 中的 flatMap
操作。理解 flatMap
的关键在于将其与 map
操作进行对比。
核心概念:
map
操作:- 作用:将一个流中的每个元素转换为另一个元素(类型可以不同)。
- 输入:一个元素
T
- 输出:一个元素
R
(通过你提供的Function<T, R>
函数) - 结果流:一个包含所有转换后元素
R
的流Stream<R>
。 - 特点: 输入一个元素,输出一个元素。输入流中有 N 个元素,输出流中也有 N 个元素(类型可能变了)。
flatMap
操作:- 作用:将一个流中的每个元素转换为零个、一个或多个新元素组成的流,然后将所有这些生成的流“扁平化” 连接成一个单一的流。
- 输入:一个元素
T
- 输出:一个流
Stream<R>
(通过你提供的Function<T, Stream<R>>
函数) - 结果流:将每个输入元素
T
产生的Stream<R>
中的所有元素连接(展平) 起来形成的最终Stream<R>
。 - 特点:
- 输入一个元素,输出一个流(包含零个、一个或多个元素)。
- 然后将所有输出的流“拍扁”,合并成一个单一的流。
- 输入流中有 N 个元素,输出流中的元素个数可以是 0 到 M(M 是所有生成的流中元素的总和)。元素类型通常也会改变。
为什么叫 “flatMap”?
- Map: 因为它像
map
一样,对每个元素应用一个转换函数。 - Flat: 因为它将转换后产生的嵌套结构(流中的流) “展平”(flatten)成一个单一的流。
核心函数式接口:
map
使用Function<T, R>
:R apply(T t)
flatMap
使用Function<T, Stream<R>>
:Stream<R> apply(T t)
— 注意返回值必须是Stream
!
常见使用场景:
展平嵌套集合: 这是最经典的用法。
List<List<String>> nestedList = Arrays.asList(Arrays.asList("a", "b", "c"),Arrays.asList("d", "e"),Arrays.asList("f", "g", "h") );// 使用 map: 得到一个包含3个流的流 (Stream<Stream<String>>) Stream<Stream<String>> streamOfStreams = nestedList.stream().map(list -> list.stream());// 使用 flatMap: 得到一个包含所有字符串的单一流 (Stream<String>) Stream<String> flatStream = nestedList.stream().flatMap(list -> list.stream());List<String> flattenedList = flatStream.collect(Collectors.toList()); // flattenedList 结果为: ["a", "b", "c", "d", "e", "f", "g", "h"]
- 处理可能产生多个结果的元素: 例如,一个字符串数组,你想把每个字符串按空格分割成单词。
List<String> lines = Arrays.asList("Hello world", "Java 8 Streams", "flatMap example");// 使用 map + split: 得到一个包含字符串数组的流 (Stream<String[]>) Stream<String[]> streamOfArrays = lines.stream().map(line -> line.split(" "));// 使用 flatMap + Arrays.stream(): 得到一个包含所有单词的单一流 (Stream<String>) Stream<String> wordsStream = lines.stream().flatMap(line -> Arrays.stream(line.split(" ")));List<String> words = wordsStream.collect(Collectors.toList()); // words 结果为: ["Hello", "world", "Java", "8", "Streams", "flatMap", "example"]
总结 flatMap
:
- 目的: 处理那些一个输入元素会映射到多个输出元素(或者需要表示为流)的场景,并将这些输出平滑地合并到最终的结果流中。
- 关键: 你提供给
flatMap
的函数 (Function<T, Stream<R>>
) 必须返回一个Stream
对象。 - 结果: 将输入流中每个元素产生的所有
Stream<R>
的内容连接起来,形成一个单一的、连续的Stream<R>
。 - 效果: “展平”嵌套结构或一对多映射。
简单来说,当你遇到类似“我有一个列表,里面的每个元素本身又是一个列表(或其他集合),我想把它们合并成一个大列表”或者“我有一个流,每个元素处理后会得到一组结果,我想把这些结果都收集到一个流里”的情况时,flatMap
就是你的好帮手。