Stream::mapMulti
中間操作に分類される新しい方法です。
BiConsumer<T, Consumer<R>> mapper
処理しようとしている要素のが必要Consumer
です。map
後者は、 、filter
、またはpeek
のバリエーションを使用しない他の中間メソッドで慣れているものとは異なるため、一見するとメソッドが奇妙に見えます*Consumer
。
Consumer
API 自体によってラムダ式内で提供される権利の目的は、後続のパイプラインで使用できる任意の数の要素を受け入れることです。したがって、要素の数に関係なく、すべての要素が伝播されます。
簡単なスニペットを使用した説明
1 対いくつか (0..1) のマッピング( と同様filter
)
consumer.accept(R r)
選択した少数のアイテムのみにを使用すると、フィルターに似たパイプラインが実現します。これは、述語に対して要素をチェックし、それが別の値にマッピングされている場合に役立ちます。それ以外の場合は、代わりにfilter
andの組み合わせを使用して行われ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
なります。になります、、、。そして無になる。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には、このメソッドを使用する方が好ましい場合の 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();
上記のように、 、 、 などのバリエーションmapMultiToDouble
がmapMultiToInt
導入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..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
を使用した後者のスニペットに見られる、以前のバージョンの Stream メソッドの組み合わせの宣言的アプローチと比較して、より命令的な傾向があります。この観点から、命令型アプローチを使用する方が使いやすいかどうかは、ユースケースによって異なります。再帰はJavaDocで説明されている良い例です。flatMap
map
filter
基準
約束通り、私はコメントから集めたアイデアから一連のマイクロ ベンチマークを作成しました。公開するコードがかなりある限り、実装の詳細を含む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