我一直在浏览Java 16的新闻和源代码,并且遇到了名为mapMulti
的新Stream方法。早期访问的JavaDoc表示它类似于flatMap
,并且已经被批准使用相同的Java版本。
<R> Stream<R> mapMulti(BiConsumer<? super T,? super Consumer<R>> mapper)
flatMap
有何不同。什么时候更可取? mapper
多少次? 最佳答案
Stream::mapMulti
是被分类为中间操作的新方法。
它要求将要处理的元素的BiConsumer<T, Consumer<R>> mapper
成为Consumer
。乍一看使该方法看起来很奇怪,因为它不同于我们在其他中间方法(例如map
,filter
或peek
)中所使用的方法,在这些中间方法中,它们都不使用*Consumer
的任何变体。
API本身在lambda表达式内提供的Consumer
的目的是接受在后续管道中可用的任何数字元素。因此,所有元素,无论有多少,都将被传播。
使用简单片段的说明
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
与flatMap比较
这种机制的初衷是可以被多次调用(包括零次),并且它在内部使用
SpinedBuffer
可以将元素插入一个扁平的Stream实例中,而无需像flatMap
那样为每组输出元素创建一个新的元素。 JavaDoc声明了两个用例,这比flatMap
更可取:在性能方面,在这种情况下,新方法
mapMulti
是赢家。在此答案的底部查看基准。筛选器 map 方案
由于这种方法的冗长性,并且无论如何都会创建一个中间流,因此单独使用此方法而不是
filter
或map
是没有意义的。异常(exception)可能是替换一起调用的.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
之类的变体。这与原始流(例如 mapMulti
)中的IntStream mapMulti(IntStream.IntMapMultiConsumer mapper)
方法一起出现。此外,还引入了三个新的功能接口(interface)。基本上,它们是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());
与在后一小段使用mapMulti
,flatMap
和map
的前一版本Stream方法组合的声明性方法相比,filter
的使用更具有命令性。从这个角度看,是否更容易使用命令式方法取决于用例。递归是JavaDoc中描述的一个很好的例子。基准测试
如所 promise 的,我已经从评论中收集的想法写了很多微基准。只要要发布的代码很多,我就创建了一个带有实现细节的GitHub repository,我将只分享结果。
Stream::flatMap(Function)
与Stream::mapMulti(BiConsumer)
Source在这里,我们可以看到巨大的区别,并证明了该新方法实际上可以按所述方法工作,并且其用法避免了为每个处理的元素创建新的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)
与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)
和Optional::stream()
的 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
关于java - 何时以及如何执行从Java 16开始在flatMap上从0到0..n映射流mapMulti,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/64132803/