これまでに、collect() メソッドを何度か使用して、Stream から返された要素を ArrayList に結合しました。これはreduce操作であり、コレクションを別の型(通常は可変コレクション)に変換するのに役立ちます。このセクションで紹介するように、collect() 関数を Collectors ツール クラスのいくつかのメソッドと組み合わせて使用すると、非常に便利になります。
引き続き前の人物リストを例として使用して、collect() メソッドで何ができるかを見てみましょう。元のリストから 20 歳以上のすべての人々を検索したいとします。以下は、可変性と forEach() メソッドを使用して実装されたバージョンです。
次のようにコードをコピーします。
List<人> oldThan20 = 新しい ArrayList<>();
.filter(person -> person.getAge() > 20)
.forEach(人 -> 年長20.add(人)); System.out.println("20歳以上の人: " + 年長20人);
filter() メソッドを使用して、20 歳以上のすべての人々をリストから除外します。次に、forEach メソッドで、以前に初期化された ArrayList に要素を追加します。まずこのコードの出力を見て、後で再構築しましょう。
次のようにコードをコピーします。
20 歳以上の人: [サラ - 21 歳、ジェーン - 21 歳、グレッグ - 35 歳]
プログラムの出力は正しいですが、まだ少し問題があります。まず、コレクションへの要素の追加は低レベルの操作であり、宣言的ではなく命令的です。この反復を同時実行に変換したい場合は、スレッドの安全性の問題を考慮する必要があります。可変性により並列化が困難になります。幸いなことに、この問題は、collect() メソッドを使用して簡単に解決できます。これがどのように実現されるかを見てみましょう。
collect() メソッドは Stream を受け取り、それらを結果コンテナーに収集します。これを行うには、次の 3 つのことを知る必要があります。
+ 結果コンテナーを作成する方法 (たとえば、ArrayList::new メソッドを使用) + 単一の要素をコンテナーに追加する方法 (たとえば、ArrayList::add メソッドを使用) + 1 つの結果セットを別の結果セットにマージする方法(たとえば、ArrayList::addAll メソッドを使用する)
最後の項目はシリアル操作には必要ありません。コードはシリアル操作と並列操作の両方をサポートするように設計されています。
これらの操作をcollectメソッドに提供し、フィルタリングされたストリームを収集させます。
次のようにコードをコピーします。
リスト<人> 20 歳以上 =
people.stream()
.filter(person -> person.getAge() > 20)
.collect(ArrayList::new, ArrayList::add, ArrayList::addAll);
System.out.println("20 歳以上の人: " + oldThan20);
このコードの結果は前と同じですが、この方法で記述することには多くの利点があります。
まず第一に、私たちのプログラミング方法はより焦点を絞った表現力豊かなもので、結果を ArrayList に収集する目的を明確に伝えています。 collect() の最初のパラメータはファクトリまたはプロデューサで、後続のパラメータは要素を収集するために使用される操作です。
第 2 に、コード内で明示的な変更を実行していないため、この反復を並列で簡単に実行できます。基礎となるライブラリに変更を処理させ、ArrayList 自体がスレッド セーフではない場合でも、ライブラリが調整とスレッド セーフの問題を処理します。素晴らしい仕事です。
条件が許せば、collect() メソッドは要素を異なるサブリストに並行して追加し、スレッドセーフな方法でそれらを大きなリストにマージできます (マージ操作には最後のパラメーターが使用されます)。
リストに要素を手動で追加するよりも、collect() メソッドを使用する方が非常に多くの利点があることがわかりました。このメソッドのオーバーロードされたバージョンを見てみましょう。これはよりシンプルで便利です。パラメータとして Collector を受け取ります。このコレクタは、プロデューサ、加算器、コンバイナを含むインターフェイスです。以前のバージョンでは、これらの操作は独立したパラメータとしてメソッドに渡され、コレクタを使用する方が簡単であり、再利用できます。 Collectors ツール クラスは、要素を ArrayList に追加するための Collector 実装を生成できる toList メソッドを提供します。前のコードを変更して、collect() メソッドを使用してみましょう。
次のようにコードをコピーします。
リスト<人> 20 歳以上 =
people.stream()
.filter(person -> person.getAge() > 20)
.collect(Collectors.toList());
System.out.println("20 歳以上の人: " + oldThan20);
Collectors ツール クラスのcollect() メソッドの簡潔なバージョンが使用されますが、これは複数の方法で使用できます。 Collectors ツール クラスには、さまざまな収集および追加操作を実行するためのいくつかの異なるメソッドがあります。たとえば、toList() メソッドに加えて、Set に追加できる toSet() メソッド、キーと値のセットに収集するために使用できる toMap() メソッド、およびjoin() メソッド。文字列に結合できます。また、mapping()、collectingAndThen()、minBy()、maxBy()、groupingBy()などのメソッドを組み合わせて使用することもできます。
groupingBy() メソッドを使用して、人々を年齢別にグループ化してみましょう。
次のようにコードをコピーします。
Map<Integer, List<person>> peopleByAge =
people.stream()
.collect(Collectors.groupingBy(person::getAge));
System.out.println("年齢別にグループ化: " + peopleByAge);
collect() メソッドを呼び出すだけでグループ化が完了します。 groupingBy() は、ラムダ式またはメソッド参照 (これは分類関数と呼ばれます) を受け入れ、グループ化する必要があるオブジェクトの特定の属性の値を返します。関数によって返された値に従って、呼び出しコンテキスト内の要素は特定のグループに配置されます。グループ化の結果は出力で確認できます。
次のようにコードをコピーします。
年齢別にグループ化: {35=[グレッグ - 35]、20=[ジョン - 20]、21=[サラ - 21、ジェーン - 21]}
人々は年齢ごとにグループ化されています。
前の例では、人々を年齢別にグループ化しました。 groupingBy() メソッドのバリアントでは、複数の条件でグループ化できます。単純な groupingBy() メソッドは、分類子を使用して要素を収集します。一般的な groupingBy() コレクターでは、グループごとにコレクターを指定できます。言い換えれば、以下に示すように、要素はコレクション プロセス中にさまざまな分類子とコレクションを通過します。
上の例を続けると、今度は年齢でグループ化するのではなく、人々の名前を取得して年齢で並べ替えます。
次のようにコードをコピーします。
Map<Integer, List<String>> 年齢別の人物の名前 =
people.stream()
。集める(
groupingBy(人::getAge, マッピング(人::getName, toList())));
System.out.println("年齢別にグループ化された人々: " + nameOfPeopleByAge);
このバージョンの groupingBy() は 2 つのパラメータを受け入れます。1 つ目は、グループ化の条件である年齢、2 つ目は、mapping() 関数によって返される結果であるコレクターです。これらのメソッドはすべて Collectors ツール クラスから取得され、このコードに静的にインポートされます。 mapping() メソッドは 2 つのパラメータを受け入れます。1 つはマッピングに使用される属性で、もう 1 つはリストやセットなどのオブジェクトが収集される場所です。上記のコードの出力を見てみましょう。
次のようにコードをコピーします。
年齢別にグループ化された人々: {35=[グレッグ]、20=[ジョン]、21=[サラ、ジェーン]}
ご覧のとおり、人の名前は年齢ごとにグループ化されています。
もう一度組み合わせ操作を見てみましょう。名前の最初の文字でグループ化し、各グループ内の最年長の人を選択します。
次のようにコードをコピーします。
Comparator<人> byAge = Comparator.comparing(person::getAge);
マップ<文字、オプション<人物>>> 各文字の最も古い人物 =
people.stream()
.collect(groupingBy(person -> person.getName().charAt(0),
削減(BinaryOperator.maxBy(byAge))));
System.out.println("各文字の最年長者:");
System.out.println(各文字の最も古い人);
まず名前をアルファベット順に並べ替えました。これを実現するには、groupingBy() の最初のパラメータとしてラムダ式を渡します。このラムダ式は、グループ化する名前の最初の文字を返すために使用されます。 2 番目のパラメーターは、mapping() ではなくなりましたが、reduce 操作を実行します。各グループ内で、maxBy() メソッドを使用して、すべての要素から最も古い要素を取得します。多くの演算を組み合わせているため、構文は少し肥大化しているように見えますが、全体は次のようになります。名前の最初の文字でグループ化し、グループ内の最年長の文字まで処理します。このコードの出力を考えてみましょう。指定された文字で始まる名前のグループの中で最年長の人物がリストされます。
次のようにコードをコピーします。
各文字の最年長者:
{S=オプション[サラ - 21]、G=オプション[グレッグ - 35]、J=オプション[ジェーン - 21]}
私たちはすでに、collect() メソッドと Collectors ユーティリティ クラスの威力を体験しました。 IDE または JDK の公式ドキュメントで、時間をかけて Collectors ツール クラスを学習し、それが提供するさまざまなメソッドに慣れてください。次に、ラムダ式を使用していくつかのフィルターを実装します。