1. 概述
本教程将深入探讨 Java 16 引入的 Stream::mapMulti
方法。通过简洁的示例代码,我们直观演示其使用方式。重点对比它与 Stream::flatMap
的相似性,并明确在何种场景下 mapMulti
是更优选择。
提示:关于 Java Streams 的更多细节,可参考我们的 Java Streams 专题。
2. 方法签名
忽略通配符后,mapMulti
的核心签名可简化为:
<R> Stream<R> mapMulti(BiConsumer<T, Consumer<R>> mapper)
这是一个 Stream 中间操作,核心参数是 BiConsumer
函数式接口的实现:
- 接收流元素
T
- (可选)将其转换为类型
R
- 调用
mapper
的Consumer::accept
方法
Java 内部实现中,mapper
本质是一个实现 Consumer
接口的缓冲区。每次调用 Consumer::accept
时:
- 元素被积累到缓冲区
- 自动传递到流管道下游
3. 简单实现示例
场景:对整数列表执行以下操作:
List<Integer> numbers = List.of(1, 2, 3, 4, 5);
double percentage = 0.1;
List<Double> result = numbers.stream()
.<Double>mapMulti((number, consumer) -> {
if (number % 2 == 0) {
double newValue = number * (1 + percentage);
consumer.accept(newValue);
}
})
.collect(Collectors.toList());
关键点解析:
BiConsumer
实现中:- 先筛选偶数(隐式
filter
作用) - 计算
double
值(隐式map
作用) - 调用
consumer.accept()
输出
- 先筛选偶数(隐式
- 类型注解
<Double>
必需,否则编译器无法推断R
类型
对比传统写法:
List<Double> result2 = numbers.stream()
.filter(n -> n % 2 == 0)
.map(n -> n * (1 + percentage))
.collect(Collectors.toList());
优势:
mapMulti
更直接,减少中间操作调用- 支持命令式编程,元素转换更灵活
原始类型支持:
mapMultiToDouble
/mapMultiToInt
/mapMultiToLong
- 示例:计算转换后 double 值的和
double sum = numbers.stream() .mapMultiToDouble((number, consumer) -> { if (number % 2 == 0) { consumer.accept(number * (1 + percentage)); } }) .sum();
4. 更真实的示例
数据模型:
class Album {
private String name;
private List<Artist> artists;
// 构造器/getter 省略
}
class Artist {
private String name;
// 构造器/getter 省略
}
目标:收集所有「艺术家-专辑」名称对
**使用 mapMulti
**:
List<Album> albums = ...; // 专辑列表
List<Pair<String, String>> pairs = albums.stream()
.<Pair<String, String>>mapMulti((album, consumer) -> {
for (Artist artist : album.getArtists()) {
consumer.accept(new ImmutablePair<>(
artist.getName(),
album.getName()
));
}
})
.collect(Collectors.toList());
**使用 flatMap
**:
List<Pair<String, String>> pairs2 = albums.stream()
.flatMap(album -> album.getArtists().stream()
.map(artist -> new ImmutablePair<>(
artist.getName(),
album.getName()
))
)
.collect(Collectors.toList());
本质:两者均实现「一对多」转换,但实现方式不同。
5. 何时用 mapMulti
替代 flatMap
5.1 替换少量元素时
Java 官方文档指出:当每个流元素需替换为少量(可能为零)元素时,
mapMulti
可避免flatMap
为每组结果创建新流的开销。
场景:根据价格阈值筛选艺术家-专辑对
double upperCost = 10.0;
// mapMulti 实现
List<Pair<String, String>> filtered = albums.stream()
.<Pair<String, String>>mapMulti((album, consumer) -> {
if (album.getPrice() < upperCost) {
for (Artist artist : album.getArtists()) {
consumer.accept(new ImmutablePair<>(
artist.getName(),
album.getName()
));
}
}
})
.collect(Collectors.toList());
// flatMap 实现
List<Pair<String, String>> filtered2 = albums.stream()
.filter(album -> album.getPrice() < upperCost)
.flatMap(album -> album.getArtists().stream()
.map(artist -> new ImmutablePair<>(
artist.getName(),
album.getName()
))
)
.collect(Collectors.toList());
性能优势:
mapMulti
无需为每个元素创建中间流- 命令式实现减少对象分配开销
5.2 生成结果元素更简单时
场景:在 Album
类中实现复杂逻辑,将艺术家-专辑对及关联厂牌传递给消费者
class Album {
// ... 其他代码
public void artistAlbumPairsToMajorLabels(Consumer<Pair<String, String>> consumer) {
for (Artist artist : artists) {
List<String> labels = artist.getMajorLabels();
if (labels != null && !labels.isEmpty()) {
String labelsStr = String.join(",", labels);
consumer.accept(new ImmutablePair<>(
artist.getName(),
this.name + " (" + labelsStr + ")"
));
}
}
}
}
调用方式:
List<Pair<String, String>> labeledPairs = albums.stream()
.<Pair<String, String>>mapMulti(Album::artistAlbumPairsToMajorLabels)
.collect(Collectors.toList());
优势:
- 支持复杂逻辑(如递归,见 Java 文档示例)
- 用
flatMap
实现此类逻辑会极其复杂
6. 结论
mapMulti
在以下场景展现明显优势:
- 少量元素替换:避免
flatMap
的中间流创建开销 - 命令式元素生成:当用
Consumer
传递元素比返回Stream
更简单时
源码见 GitHub 仓库