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教程。