java - 何时以及如何执行从Java 16开始在flatMap上从0到0..n映射流mapMulti

标签 java java-stream flatmap java-16 mapmulti

我一直在浏览Java 16的新闻和源代码,并且遇到了名为mapMulti的新Stream方法。早期访问的JavaDoc表示它类似于flatMap,并且已经被批准使用相同的Java版本。

<R> Stream<R> mapMulti​(BiConsumer<? super T,​? super Consumer<R>> mapper)
  • 如何使用此方法执行从1到0..n的映射?
  • 新方法如何工作,与flatMap有何不同。什么时候更可取?
  • 可以调用mapper多少次?
  • 最佳答案

    Stream::mapMulti是被分类为中间操作的新方法。
    它要求将要处理的元素的BiConsumer<T, Consumer<R>> mapper成为Consumer。乍一看使该方法看起来很奇怪,因为它不同于我们在其他中间方法(例如mapfilterpeek)中所使用的方法,在这些中间方法中,它们都不使用*Consumer的任何变体。
    API本身在lambda表达式内提供的Consumer的目的是接受在后续管道中可用的任何数字元素。因此,所有元素,无论有多少,都将被传播。
    使用简单片段的说明

  • 一对多(0..1)映射(类似于filter)
    仅将consumer.accept(R r)用于少数几个选定的项目即可实现类似过滤器的管道。在根据谓词检查元素并将其映射到其他值的情况下,这可能会很有用,否则可以使用filtermap的组合来完成。以下
    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变为224变为44440变为零。
    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更可取:
    • When replacing each stream element with a small (possibly zero) number of elements. Using this method avoids the overhead of creating a new Stream instance for every group of result elements, as required by flatMap.
    • When it is easier to use an imperative approach for generating result elements than it is to return them in the form of a Stream.

    在性能方面,在这种情况下,新方法mapMulti是赢家。在此答案的底部查看基准。
    筛选器 map 方案
    由于这种方法的冗长性,并且无论如何都会创建一个中间流,因此单独使用此方法而不是filtermap是没有意义的。异常(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优惠的平面映射,并基于某些条件(产品类别和变体可用性)。

    带有ProductString nameint basePriceString category
  • List<Variation> variations
  • VariationString nameint priceboolean 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());
    
    与在后一小段使用mapMultiflatMapmap的前一版本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/

    相关文章:

    java - Java 和 .NET 的测试和模拟框架

    java - 如何使用 Quarkus Panache 仅选择某些字段?

    java - 是否有带多个队列的开箱即用的线程池(确保每个队列的串行处理)?

    java - 如何在 Java 中使用 Flatmap 和个性化对象

    swift - 为什么 range() 和 sequenceOf() 在 RxSwift 中与 flatMap 的行为不同

    java - 将枚举视为类中的常规属性

    java - 在Java中应用MapReduce

    Java 8 collect() only isPresent() 可选值

    Java流映射

    scala - 如何 flatMap cats Applicatives