Java Stream: flatMap vs mapMulti
最后修改时间:2025 年 6 月 8 日
在使用 Java Streams 时,开发人员经常需要转换和扩展流元素。Java 提供了两种主要方法来实现此目的:flatMap
(自 Java 8 起)和 mapMulti
(Java 16 中引入)。本教程探讨了它们的区别、性能特征和理想用例。
虽然这两种方法都可以将每个流元素转换为零个或多个输出元素,但它们遵循不同的范例。flatMap
使用一种函数式方法,需要新的 Stream 实例,而 mapMulti
使用一种命令式、基于消费者的的方法,在某些情况下可能更有效。
关键差异概述
下表总结了 Java Streams 中 mapMulti
和 flatMap
之间的关键差异。
功能 | mapMulti | flatMap |
---|---|---|
引入版本 | Java 16 | Java 8 |
编程风格 | 命令式(推送) | 函数式(拉取) |
性能 | 更适合简单的扩展 | 更适合复杂的转换 |
中间对象 | 没有中间流 | 创建中间流 |
可读性 | 更适合命令式逻辑 | 更适合函数式管道 |
这两种方法都可以实现类似的结果,但它们的性能和可读性会根据具体用例而有很大差异。了解这些差异有助于您为流处理需求选择正确的工具。
基本元素扩展
此示例展示了这两种方法如何将每个输入元素扩展为多个输出元素。我们将字符串转换为它们的大写和小写变体。
void main() { List<String> words = List.of("apple", "banana", "cherry"); List<String> result = words.stream() .flatMap(word -> Stream.of(word.toUpperCase(), word.toLowerCase())) .toList(); System.out.println(result); }
此代码使用 flatMap
将每个单词转换为两个元素:它的大写和小写版本。flatMap
方法接受一个函数,该函数为每个输入元素返回一个 Stream,然后将其扁平化为单个输出流。
mapMulti
方法使用 BiConsumer
为每个输入发出多个元素。consumer 直接接受转换后的元素,避免了创建中间 Stream
实例的需要。
void main() { List<String> words = List.of("apple", "banana", "cherry"); List<String> result = words.stream() .<String>mapMulti((word, consumer) -> { consumer.accept(word.toUpperCase()); consumer.accept(word.toLowerCase()); }) .toList(); System.out.println(result); }
两个版本产生相同的输出,但 mapMulti
避免了创建中间 Stream 实例。对于像这样的简单扩展,mapMulti
通常表现出更好的性能,同时保持可读性。
条件元素扩展
当您需要根据某些条件有条件地扩展元素时,两种方法之间的差异会变得更加明显。
void main() { List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6); List<Integer> result = numbers.stream() .flatMap(n -> { if (n % 2 == 0) { return Stream.of(n, n * 10); } return Stream.empty(); }) .toList(); System.out.println(result); }
此代码使用 flatMap
将偶数转换为两个元素:数字本身及其十倍。如果数字是奇数,它将返回一个空 Stream。这种方法可行,但在处理多个条件或复杂逻辑时,可能会导致代码可读性降低。
void main() { List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6); List<Integer> result = numbers.stream() .<Integer>mapMulti((n, consumer) -> { if (n % 2 == 0) { consumer.accept(n); consumer.accept(n * 10); } }) .toList(); System.out.println(result); }
mapMulti
版本更自然地处理条件逻辑,而无需显式返回空流。这使得在处理多个条件或复杂的 branching 逻辑时,代码更加简单明了。
何时使用哪种方法
maptMulti
和 flatMap
方法各有优势和理想用例。 了解何时使用每种方法可以帮助您编写更高效、更易读的代码。
在某些情况下,mapMulti
方法比 flatMap
具有多个优势
- 没有中间 Stream 对象分配
- 减少 lambda 捕获
- JIT 编译器更好的内联机会
当您需要执行简单扩展(1 对少数元素)或想要以更命令式的方式处理复杂的条件逻辑时,mapMulti
方法特别有用。它允许您直接发出元素,而无需创建中间 Stream 对象,这可以在某些情况下提高性能。
在以下情况下,首选 mapMulti
- 您需要对元素发射进行命令式控制
- 执行简单扩展(1 对少数元素)
- 处理复杂的条件逻辑
- 处理有状态的转换
- 对于简单的情况,性能至关重要
在以下情况下,首选 flatMap
- 您的转换自然地返回一个 Stream
- 使用返回 Streams 的现有方法
- 链接函数式操作
- 可读性比小的性能提升更重要
- 转换为多个元素(1 对多)
真实示例:订单处理
让我们研究一个更实际的例子,处理包含嵌套项的电子商务订单。
record Order(String id, List<Item> items) {} record Item(String sku, int quantity, double price) {} void main() { List<Order> orders = List.of( new Order("O1", List.of( new Item("I1", 2, 9.99), new Item("I2", 1, 19.99) )), new Order("O2", List.of( new Item("I3", 5, 4.99) )) ); List<String> result = orders.stream() .flatMap(order -> order.items().stream() .filter(item -> item.quantity() > 1) .map(item -> "%s: %s x %.2f".formatted( order.id(), item.sku(), item.price() * item.quantity() )) ) .toList(); System.out.println(result); }
此代码使用 flatMap
将每个单词转换为两个元素:它的大写和小写版本。flatMap
方法接受一个函数,该函数为每个输入元素返回一个 Stream,然后将其扁平化为单个输出流。
record Order(String id, List<Item> items) {} record Item(String sku, int quantity, double price) {} void main() { List<Order> orders = List.of( new Order("O1", List.of( new Item("I1", 2, 9.99), new Item("I2", 1, 19.99) )), new Order("O2", List.of( new Item("I3", 5, 4.99) )) ); List<String> result = orders.stream() .<String>mapMulti((order, consumer) -> { for (Item item : order.items()) { if (item.quantity() > 1) { String line = "%s: %s x %.2f".formatted( order.id(), item.sku(), item.price() * item.quantity() ); consumer.accept(line); } } }) .toList(); System.out.println(result); }
在这个真实的场景中,这两种方法都有效。当已经使用返回 Streams 的方法时,flatMap
版本可能更合适,而 mapMulti
在处理复杂条件时提供更好的性能和更直接的命令式逻辑。
迁移技巧
从 flatMap
迁移到 mapMulti
时
- 将返回 Stream 的函数替换为 BiConsumer
- 将元素发射从
return Stream.of(x)
更改为consumer.accept(x)
- 将过滤逻辑移动到发射之前,而不是使用
filter
- 对于空结果,只需不调用 consumer
请记住,迁移并非总是必要的 - flatMap
仍然是许多场景的不错选择,尤其是在使用现有的返回 Stream 的方法时。
来源
Java Stream mapMulti 文档
Java Stream flatMap 文档
mapMulti
和 flatMap
都是 Stream API 中的宝贵工具。mapMulti
提供了一种命令式的替代方案,对于某些模式来说可能更有效,而 flatMap
仍然是一种更函数式的方法。 根据您的具体要求、编码风格和性能需求进行选择。
作者
列出所有Java教程。