Stream::mapMulti
是一种分类为中间操作的新方法。
它需要将要处理的元素的 a。后者使该方法乍一看很奇怪,因为它与我们在其他中间方法(如 、或 )中所习惯的方法不同,或者它们都不使用 的任何变体。BiConsumer<T, Consumer<R>> mapper
Consumer
map
filter
peek
*Consumer
API 本身在 lambda 表达式中提供的权利的目的是接受在后续管道中可用的任何数字元素。因此,所有元素,无论有多少,都将被传播。Consumer
使用简单代码段的解释
-
一对一(0..1)映射(类似于filter
)
仅对少数选定项使用 可实现类似筛选器的管道。如果根据谓词检查元素并将其映射到不同的值,这可能会很有用,否则将使用 和 的组合来完成。以下consumer.accept(R r)
filter
map
Stream.of("Java", "Python", "JavaScript", "C#", "Ruby")
.mapMulti((str, consumer) -> {
if (str.length() > 4) {
consumer.accept(str.length()); // lengths larger than 4
}
})
.forEach(i -> System.out.print(i + " "));
// 6 10
-
一对一映射(类似于map
)
使用前面的示例,当省略条件并且每个元素都映射到一个新元素并使用 接受时,该方法实际上表现得像:consumer
map
Stream.of("Java", "Python", "JavaScript", "C#", "Ruby")
.mapMulti((str, consumer) -> consumer.accept(str.length()))
.forEach(i -> System.out.print(i + " "));
// 4 6 10 2 4
-
一对多映射(类似于flatMap
)
在这里,事情变得有趣,因为人们可以拨打任意次数。假设我们想要复制表示字符串长度的数字本身,即 成为。 成为。并变得一无所有。consumer.accept(R r)
2
2
2
4
4
4
4
4
0
Stream.of("Java", "Python", "JavaScript", "C#", "Ruby", "")
.mapMulti((str, consumer) -> {
for (int i = 0; i < str.length(); i++) {
consumer.accept(str.length());
}
})
.forEach(i -> System.out.print(i + " "));
// 4 4 4 4 6 6 6 6 6 6 10 10 10 10 10 10 10 10 10 10 2 2 4 4 4 4
与平面地图的比较
这种机制的理念是可以多次调用(包括零),并且它在内部的使用允许将元素推送到单个扁平化的Stream实例中,而无需为每组输出元素创建一个新元素,这与.JavaDoc 指出了两个用例,当使用此方法优于以下方法时:SpinedBuffer
flatMap
flatMap
- 将每个流元素替换为少量(可能为零)元素时。使用此方法可避免根据 flatMap 的要求为每组结果元素创建新的 Stream 实例的开销。
- 当使用命令式方法生成结果元素比以 Stream 的形式返回结果元素更容易时。
在性能方面,在这种情况下,新方法是赢家。查看此答案底部的基准测试。mapMulti
筛选器映射方案
使用此方法代替或单独使用此方法没有意义,因为它很长,而且无论如何都会创建一个中间流。例外情况可能是替换一起调用的链,这在检查元素类型及其强制转换等情况下会很方便。filter
map
.filter(..).map(..)
int sum = Stream.of(1, 2.0, 3.0, 4F, 5, 6L)
.mapMultiToInt((number, consumer) -> {
if (number instanceof Integer) {
consumer.accept((Integer) number);
}
})
.sum();
// 6
int sum = Stream.of(1, 2.0, 3.0, 4F, 5, 6L)
.filter(number -> number instanceof Integer)
.mapToInt(number -> (Integer) number)
.sum();
如上所示,它的变化,如mapMultiToDouble
,mapMultiToInt
和mapMultiToLong
被引入。这伴随着原始流中的方法,例如IntStream mapMulti(IntStream.IntMapMultiConsumer mapper)。
此外,还引入了三个新的功能接口。基本上,它们是 的原始变体,例如:mapMulti
BiConsumer<T, Consumer<R>>
@FunctionalInterface
interface IntMapMultiConsumer {
void accept(int value, IntConsumer ic);
}
组合的真实用例场景
此方法的真正功能在于其使用灵活性,并且一次只能创建一个Stream,这是与.以下两个代码段表示由类表示的商品的平面映射,并基于特定条件(产品类别和变体可用性)。flatMap
Product
List<Variation>
0..n
Offer
-
Product
与 、 和 。String name
int basePrice
String category
List<Variation> variations
-
Variation
与 和 。String name
int price
boolean availability
List<Product> products = ...
List<Offer> offers = products.stream()
.mapMulti((product, consumer) -> {
if ("PRODUCT_CATEGORY".equals(product.getCategory())) {
for (Variation v : product.getVariations()) {
if (v.isAvailable()) {
Offer offer = new Offer(
product.getName() + "_" + v.getName(),
product.getBasePrice() + v.getPrice());
consumer.accept(offer);
}
}
}
})
.collect(Collectors.toList());
List<Product> products = ...
List<Offer> offers = products.stream()
.filter(product -> "PRODUCT_CATEGORY".equals(product.getCategory()))
.flatMap(product -> product.getVariations().stream()
.filter(Variation::isAvailable)
.map(v -> new Offer(
product.getName() + "_" + v.getName(),
product.getBasePrice() + v.getPrice()
))
)
.collect(Collectors.toList());
与以前版本的 Stream 方法组合的声明性方法相比,使用 更倾向于使用 、 和 。从这个角度来看,这取决于用例是否更容易使用命令式方法。递归是 JavaDoc 中描述的一个很好的例子。mapMulti
flatMap
map
filter
基准
正如承诺的那样,我从评论中收集的想法中写了一堆微观基准。只要有相当多的代码要发布,我就创建了一个包含实现细节的GitHub存储库,我将只共享结果。
流::平面地图(函数)
vs 流::地图多(双消费者)
来源
在这里,我们可以看到巨大的差异和证明,较新的方法实际上按所述工作,并且它的使用避免了为每个处理的元素创建新的Stream实例的开销。
Benchmark Mode Cnt Score Error Units
MapMulti_FlatMap.flatMap avgt 25 73.852 ± 3.433 ns/op
MapMulti_FlatMap.mapMulti avgt 25 17.495 ± 0.476 ns/op
Stream::filter(Predicate).map(Function)
vs Stream::mapMulti(BiConsumer)
Source
使用链接管道(不是嵌套的)是可以的。
Benchmark Mode Cnt Score Error Units
MapMulti_FilterMap.filterMap avgt 25 7.973 ± 0.378 ns/op
MapMulti_FilterMap.mapMulti avgt 25 7.765 ± 0.633 ns/op
Stream::flatMap(Function)
with Optional::stream()
vs Stream::mapMulti(BiConsumer)
Source
这个非常有趣,特别是在用法方面(参见源代码):我们现在能够使用扁平化,并且正如预期的那样,在这种情况下,新方法会更快一些。mapMulti(Optional::ifPresent)
Benchmark Mode Cnt Score Error Units
MapMulti_FlatMap_Optional.flatMap avgt 25 20.186 ± 1.305 ns/op
MapMulti_FlatMap_Optional.mapMulti avgt 25 10.498 ± 0.403 ns/op