23

最新の LTE Java 17 バージョンのニュースとソース コードにざっと目を通していると、新しい Stream メソッドと呼ばれるmapMulti. 早期アクセスJavaDocには、flatMap.

<R> Stream<R> mapMulti(BiConsumer<? super T,? super Consumer<R>> mapper)
  • この方法を使用して 1 から 0..n へのマッピングを実行する方法は?
  • 新しい方法はどのように機能し、 とどのように異なるのですかflatMap。それぞれが望ましいのはいつですか?
  • は何回mapper呼び出すことができますか?
4

2 に答える 2

35

Stream::mapMulti中間操作に分類される新しい方法です。

BiConsumer<T, Consumer<R>> mapper処理しようとしている要素のが必要Consumerです。map後者は、 、filter、またはpeekのバリエーションを使用しない他の中間メソッドで慣れているものとは異なるため、一見するとメソッドが奇妙に見えます*Consumer

ConsumerAPI 自体によってラムダ式内で提供される権利の目的は、後続のパイプラインで使用できる任意の数の要素を受け入れることです。したがって、要素の数に関係なく、すべての要素が伝播されます。

簡単なスニペットを使用した説明

  • 1 対いくつか (0..1) のマッピング( と同様filter)

    consumer.accept(R r)選択した少数のアイテムのみにを使用すると、フィルターに似たパイプラインが実現します。これは、述語に対して要素をチェックし、それが別の値にマッピングされている場合に役立ちます。それ以外の場合は、代わりにfilterandの組み合わせを使用して行われ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
    
  • 1 対 1 のマッピング( と同様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
    
  • 1 対多のマッピング( と同様flatMap)

    ここで面白いのは、何回consumer.accept(R r) でも呼び出すことができるということです。文字列の長さを表す数値をそれ自体で複製したいとしましょう。つまり、 に2なります。になります、、、。そして無になる。22444440

    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には、このメソッドを使用する方が好ましい場合の 2 つの使用例が記載さていflatMapます。

  • 各ストリーム要素を少数の (ゼロの可能性もある) 要素に置き換える場合。このメソッドを使用すると、flatMap で必要な結果要素のグループごとに新しい Stream インスタンスを作成するオーバーヘッドが回避されます。
  • 結果要素を生成するために命令型アプローチを使用する方が、ストリームの形式で返すよりも簡単な場合。

このような場合、パフォーマンスに関しては、新しい方法mapMultiが勝者です。この回答の下部にあるベンチマークを確認してください。

フィルター マップのシナリオ

このメソッドの代わりに、filterまたはmap個別に使用することは、その冗長性と、とにかく 1 つの中間ストリームが作成されるという事実のために意味がありません。例外は、要素の型とそのキャストをチェックする場合などに便利な、together と呼ばれる.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();

上記のように、 、 、 などのバリエーションmapMultiToDoublemapMultiToInt導入mapMultiToLongされました。これは、mapMultiなどのプリミティブ Streams 内のメソッドに沿って発生しIntStream mapMulti​(IntStream.IntMapMultiConsumer mapper)ます。また、3 つの新しい機能インターフェイスが導入されました。基本的に、それらは のプリミティブなバリエーションですBiConsumer<T, Consumer<R>>。例:

@FunctionalInterface
interface IntMapMultiConsumer {
    void accept(int value, IntConsumer ic);
}

組み合わせた実際のユースケース シナリオ

この方法の真価は、柔軟に使用できることと、一度に 1 つのストリームのみを作成できることです。これは、 よりも大きな利点flatMapです。以下の 2 つのスニペットは、特定の条件 (製品カテゴリとバリエーションの可用性) に基づいて、クラスによって表されるオファーProductとそのオファーList<Variation>へのフラットマッピングを表しています。0..nOffer

  • ProductString name、および。int basePrice_String categoryList<Variation> variations
  • Variation、および。String name_int 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());

の使用は、 、、およびmapMultiを使用した後者のスニペットに見られる、以前のバージョンの Stream メソッドの組み合わせの宣言的アプローチと比較して、より命令的な傾向があります。この観点から、命令型アプローチを使用する方が使いやすいかどうかは、ユースケースによって異なります。再帰はJavaDocで説明されている良い例です。flatMapmapfilter

基準

約束通り、私はコメントから集めたアイデアから一連のマイクロ ベンチマークを作成しました。公開するコードがかなりある限り、実装の詳細を含むGitHub リポジトリを作成しました。結果のみを共有しようとしています。

Stream::flatMap(Function)Stream::mapMulti(BiConsumer) ソース

ここでは、大きな違いと、新しいメソッドが実際に説明どおりに機能し、その使用により、処理された各要素で新しい 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) ソース

チェーン化されたパイプライン (ただし、ネストされていません) を使用しても問題ありません。

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) ソース_

これは非常に興味深いもので、特に使用法 (ソース コードを参照) の点で: 使用してフラット化できるように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
于 2020-09-30T07:27:02.207 に答える