Java 8 groupingBy Collectorのガイド
1. 前書き
この記事では、さまざまな例を使用して、groupingByコレクターがどのように機能するかを確認します。
この記事で説明されている資料を理解するには、Java 8の機能の基本的な知識が必要です。 intro to Java 8 Streamsとguide to Java 8’s Collectorsを見ることができます。
参考文献:
2. GroupingBy Collectors
Java 8Stream APIを使用すると、データのコレクションを宣言的な方法で処理できます。
静的ファクトリメソッドCollectors.groupingBy()およびCollectors.groupingByConcurrent()は、SQL言語の ‘GROUP BY'句と同様の機能を提供します。 これらは、オブジェクトをいくつかのプロパティでグループ化し、結果をMapインスタンスに格納するために使用されます。
groupingByのオーバーロードされたメソッド:
-
メソッドパラメータとして分類関数を使用する場合:
static Collector>>
groupingBy(Function super T,? extends K> classifier)
-
メソッドパラメーターとして分類関数と2番目のコレクターを使用する場合:
static Collector>
groupingBy(Function super T,? extends K> classifier,
Collector super T,A,D> downstream)
-
分類関数、サプライヤーメソッド(最終結果を含むMap実装を提供する)、およびメソッドパラメーターとしての2番目のコレクターを使用します。
static > Collector
groupingBy(Function super T,? extends K> classifier,
Supplier mapFactory, Collector super T,A,D> downstream)
2.1. コード設定の例
groupingBy()の使用法を示すために、BlogPostクラスを定義しましょう(BlogPostオブジェクトのストリームを使用します)。
class BlogPost {
String title;
String author;
BlogPostType type;
int likes;
}
BlogPostType:
enum BlogPostType {
NEWS,
REVIEW,
GUIDE
}
BlogPostオブジェクトのList:
List posts = Arrays.asList( ... );
また、type属性とauthor属性の組み合わせによって投稿をグループ化するために使用されるTupleクラスを定義しましょう。
class Tuple {
BlogPostType type;
String author;
}
2.2. 単一の列による単純なグループ化
最も単純なgroupingByメソッドから始めましょう。このメソッドは、パラメーターとして分類関数のみを取ります。 分類関数は、ストリームの各要素に適用されます。 関数によって返される値は、groupingByコレクターから取得するマップへのキーとして使用されます。
ブログ投稿リスト内のブログ投稿をtypeでグループ化するには:
Map> postsPerType = posts.stream()
.collect(groupingBy(BlogPost::getType));
2.3. 複雑なMapキータイプでグループ化
分類関数は、スカラー値または文字列値のみを返すことに限定されません。 結果のマップのキーは、必要なequalsおよびhashcodeメソッドを実装していることを確認する限り、任意のオブジェクトにすることができます。
リスト内のブログ投稿をTupleインスタンスで結合されたtypeとauthorでグループ化するには:
Map> postsPerTypeAndAuthor = posts.stream()
.collect(groupingBy(post -> new Tuple(post.getType(), post.getAuthor())));
2.4. 返されたMap値タイプの変更
groupingByの2番目の過負荷は、追加の2番目のコレクター(ダウンストリームコレクター)を取ります。これは、最初のコレクターの結果に適用されます。
分類関数のみを指定し、ダウンストリームコレクターを指定しない場合、toList()コレクターがバックグラウンドで使用されます。
ダウンストリームコレクターとしてtoSet()コレクターを使用し、(Listの代わりに)ブログ投稿のSetを取得しましょう。
Map> postsPerType = posts.stream()
.collect(groupingBy(BlogPost::getType, toSet()));
2.5. 複数のフィールドによるグループ化
ダウンストリームコレクターの別の用途は、最初のgroup byの結果に対して2次グループ化を行うことです。
BlogPostsのListを最初にauthorでグループ化し、次にtypeでグループ化するには:
Map> map = posts.stream()
.collect(groupingBy(BlogPost::getAuthor, groupingBy(BlogPost::getType)));
2.6. グループ化された結果から平均を取得する
ダウンストリームコレクターを使用することにより、分類関数の結果に集計関数を適用できます。
各ブログ投稿typeのlikesの平均数を見つけるには:
Map averageLikesPerType = posts.stream()
.collect(groupingBy(BlogPost::getType, averagingInt(BlogPost::getLikes)));
2.7. グループ化された結果から合計を取得する
各typeのlikesの合計を計算するには:
Map likesPerType = posts.stream()
.collect(groupingBy(BlogPost::getType, summingInt(BlogPost::getLikes)));
2.8. グループ化された結果から最大値または最小値を取得する
実行できる別の集計は、いいねの最大数でブログ投稿を取得することです。
Map> maxLikesPerPostType = posts.stream()
.collect(groupingBy(BlogPost::getType,
maxBy(comparingInt(BlogPost::getLikes))));
同様に、minByダウンストリームコレクターを適用して、最小数のlikesでブログ投稿を取得できます。
maxByおよびminByコレクターは、それが適用されるコレクションが空である可能性を考慮に入れていることに注意してください。 これが、マップの値タイプがOptional<BlogPost>である理由です。
2.9. グループ化された結果の属性の要約を取得する
Collectors APIは、数値属性のカウント、合計、最小、最大、および平均を同時に計算する必要がある場合に使用できる要約コレクターを提供します。
さまざまなタイプごとに、ブログ投稿の「いいね」属性の概要を計算してみましょう。
Map likeStatisticsPerType = posts.stream()
.collect(groupingBy(BlogPost::getType,
summarizingInt(BlogPost::getLikes)));
各タイプのIntSummaryStatisticsオブジェクトには、likes属性のカウント、合計、平均、最小、および最大値が含まれています。 doubleおよびlong値には、追加の要約オブジェクトが存在します。
2.10. グループ化された結果を別のタイプにマッピングする
分類関数の結果にmappingダウンストリームコレクターを適用することで、より複雑な集計を実現できます。
各ブログ投稿typeの投稿のtitlesを連結してみましょう。
Map postsPerType = posts.stream()
.collect(groupingBy(BlogPost::getType,
mapping(BlogPost::getTitle, joining(", ", "Post titles: [", "]"))));
ここで行ったことは、各BlogPostインスタンスをそのtitleにマップしてから、投稿タイトルのストリームを連結されたStringに減らすことです。 この例では、Map値のタイプもデフォルトのListタイプとは異なります。
2.11. 戻り値Mapタイプの変更
groupingByコレクターを使用する場合、返されるMapのタイプについて推測することはできません。 グループから取得するMapのタイプを特定したい場合は、groupingByメソッドの3番目のバリエーションを使用して、Mapのタイプを変更できます。 )sMapサプライヤー関数を渡すことによって。
EnumMapサプライヤ関数をgroupingByメソッドに渡して、EnumMapを取得しましょう。
EnumMap> postsPerType = posts.stream()
.collect(groupingBy(BlogPost::getType,
() -> new EnumMap<>(BlogPostType.class), toList()));
3. コレクターによる同時グループ化
groupingByと同様に、マルチコアアーキテクチャを活用するgroupingByConcurrentコレクターがあります。 このコレクターには、groupingByコレクターのそれぞれのオーバーロードされたメソッドとまったく同じ引数を取る3つのオーバーロードされたメソッドがあります。 ただし、groupingByConcurrentコレクターの戻り値の型は、ConcurrentHashMapクラスまたはそのサブクラスのインスタンスである必要があります。
グループ化操作を同時に実行するには、ストリームが並列である必要があります。
ConcurrentMap> postsPerType = posts.parallelStream()
.collect(groupingByConcurrent(BlogPost::getType));
Mapサプライヤー関数をgroupingByConcurrentコレクターに渡すことを選択した場合、関数がConcurrentHashMapまたはそのサブクラスのいずれかを返すことを確認する必要があります。
4. Java9の追加
Java 9は、groupingByで適切に機能する2つの新しいコレクターをもたらしました。これに関する詳細はhereにあります。
5. 結論
この記事では、Java 8Collectors APIによって提供されるgroupingByコレクターの使用例をいくつか見てきました。
groupingByを使用して、属性の1つに基づいて要素のストリームを分類する方法と、分類の結果をさらに収集、変更して、最終的なコンテナーに縮小する方法を確認しました。
この記事の例の完全な実装は、the GitHub projectにあります。