Java 8 groupingBy Collectorの手引き
1前書き
この記事では、
groupingBy
コレクターがさまざまな例を使用してどのように機能するかを説明します。
この記事で取り上げる資料を理解するためには、Java 8の機能に関する基本的な知識が必要です。/java-8-streams-Introduction[Java 8 Streamsの紹介]および/java-8-collectors[Java 8のCollectorsへのガイド]のリンクを見てください。
2
グループ化コレクター
Java 8
Stream
APIを使用すると、宣言された方法でデータのコレクションを処理できます。
静的ファクトリメソッド
Collectors.groupingBy()
および
Collectors.groupingByConcurrent()
は、SQL言語の「
GROUP BY」句に似た機能を提供します。いくつかのプロパティによってオブジェクトをグループ化し、結果を
Map__インスタンスに格納するために使用されます。
groupingBy
のオーバーロードされたメソッド:
**
static <T,K> Collector<T,?,Map<K,List<T>>>
groupingBy(Function<? super T,? extends K> classifier)
方法として分類機能そして第2コレクターを使って**
パラメーター:
static <T,K,A,D> Collector<T,?,Map<K,D>>
groupingBy(Function<? super T,? extends K> classifier,
Collector<? super T,A,D> downstream)
※分類機能付き、サプライヤ方式(
最終結果を含む
Map
実装、およびメソッドパラメータとしての2番目のコレクタ
static <T,K,D,A,M extends Map<K,D>> Collector<T,?,M>
groupingBy(Function<? super T,? extends K> classifier,
Supplier<M> 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<BlogPost> posts = Arrays.asList( ... );
type
属性と
author
属性の組み合わせによって投稿をグループ化するために使用される
Tuple
クラスも定義しましょう。
class Tuple {
BlogPostType type;
String author;
}
2.2. 単一列による単純なグループ化
パラメータとして分類関数のみを使用する、最も簡単な
groupingBy
メソッドから始めましょう。分類関数がストリームの各要素に適用されます。関数によって返される値は、
groupingBy
コレクターから取得するマップへのキーとして使用されます。
ブログ投稿リストのブログ投稿を
type
でグループ化するには、次の手順を実行します。
Map<BlogPostType, List<BlogPost>> postsPerType = posts.stream()
.collect(groupingBy(BlogPost::getType));
2.3. 複雑な
Map
キータイプを使用したグループ化
分類関数は、スカラー値またはストリング値のみを返すことに限定されていません。結果として得られるマップのキーは、必要な
equals
メソッドと
hashcode
メソッドを確実に実装する限り、任意のオブジェクトにすることができます。
Touple
インスタンスで結合された
type
と
author
でリスト内のブログ投稿をグループ化するには
Map<Tuple, List<BlogPost>> postsPerTypeAndAuthor = posts.stream()
.collect(groupingBy(post -> new Tuple(post.getType(), post.getAuthor())));
2.4. 返される
Map
値型の変更
groupingBy
の2番目のオーバーロードは、追加の2番目のコレクター(ダウンストリームコレクター)を取ります。これは、最初のコレクターの結果に適用されます。
分類関数のみを指定し、下流のコレクターを指定しない場合、
toList()
コレクターが舞台裏で使用されます。
ダウンストリームコレクターとして
toSet()
コレクターを使用し、(
List
ではなく)ブログ投稿の
Set
を取得します。
Map<BlogPostType, Set<BlogPost>> postsPerType = posts.stream()
.collect(groupingBy(BlogPost::getType, toSet()));
2.5. コレクターによるセカンダリグループの提供
ダウンストリームコレクターの別の用途は、最初のグループの結果に基づいて2次グループ化を行うことです。
__BlogPost
sの
List
を最初に
author
で、次に
type__でグループ化するには
Map<String, Map<BlogPostType, List>> map = posts.stream()
.collect(groupingBy(BlogPost::getAuthor, groupingBy(BlogPost::getType)));
2.6. グループ化された結果から平均を得る
ダウンストリームコレクタを使用することで、分類関数の結果に集計関数を適用できます。
各ブログ投稿の
likes
の平均数を見つけるには:
Map<BlogPostType, Double> averageLikesPerType = posts.stream()
.collect(groupingBy(BlogPost::getType, averagingInt(BlogPost::getLikes)));
2.7. グループ化された結果から合計を得る
type
ごとに
likes
の合計を計算するには、次のようにします。
Map<BlogPostType, Integer> likesPerType = posts.stream()
.collect(groupingBy(BlogPost::getType, summingInt(BlogPost::getLikes)));
2.8. グループ化された結果から最大値または最小値を取得する
私たちが実行できるもう一つの集約は、いいねの最大数でブログ投稿を取得することです。
Map<BlogPostType, Optional<BlogPost>> maxLikesPerPostType = posts.stream()
.collect(groupingBy(BlogPost::getType,
maxBy(comparingInt(BlogPost::getLikes))));
同様に、
minBy
ダウンストリームコレクターを適用して、最小数の
likes
のブログ投稿を取得できます。
maxBy
および
minBy
コレクターは、それが適用されるコレクションが空になる可能性を考慮に入れていることに注意してください。
これが、マップ内の値タイプが
Optional <BlogPost>
である理由です。
2.9. グループ化された結果の属性の要約を取得する
Collectors
APIは、数値属性の数、合計、最小、最大、平均を同時に計算する必要がある場合に使用できる要約コレクターを提供します。
さまざまなタイプごとに、ブログ投稿のlikes属性のサマリーを計算しましょう。
Map<BlogPostType, IntSummaryStatistics> likeStatisticsPerType = posts.stream()
.collect(groupingBy(BlogPost::getType,
summarizingInt(BlogPost::getLikes)));
各タイプの
IntSummaryStatistics
オブジェクトには、
likes
属性のcount、sum、average、min、およびmaxの値が含まれています。 double値とlong値には、追加の要約オブジェクトがあります。
2.10. グループ化された結果を異なるタイプにマッピングする
分類関数の結果にマッピング下流コレクタを適用することによって、より複雑な集約を達成することができる。
各ブログ投稿
type
の投稿のタイトルを連結してみましょう。
Map<BlogPostType, String> postsPerType = posts.stream()
.collect(groupingBy(BlogPost::getType,
mapping(BlogPost::getTitle, joining(", ", "Post titles:[", "]"))));
ここで行ったことは、各
BlogPost
インスタンスをその
title
にマッピングしてから、投稿タイトルのストリームを連結された
String
に減らすことです。この例では、
Map
値の型もデフォルトの
List
型とは異なります。
2.11. 戻り
Map
タイプの変更
groupingBy
コレクターを使用するときは、返される
Map
の型について想定することはできません。どのタイプの
Map
をグループから取得したいのかを詳しく知りたい場合は、
Map
サプライヤ関数を渡すことで
Map
のタイプを変更できるようにする
groupingBy
メソッドの3番目のバリエーションを使用できます。
EnumMap
サプライヤ関数を
groupingBy
メソッドに渡して
EnumMap
を取得しましょう。
EnumMap<BlogPostType, List<BlogPost>> postsPerType = posts.stream()
.collect(groupingBy(BlogPost::getType,
() -> new EnumMap<>(BlogPostType.class), toList()));
3コレクターによる並行グループ化
groupingBy
と同様に、マルチコアアーキテクチャを活用する
groupingByConcurrent
コレクターがあります。このコレクターには、
groupingBy
コレクターのそれぞれのオーバーロードメソッドとまったく同じ引数をとる3つのオーバーロードメソッドがあります。ただし、
groupingByConcurrent
コレクターの戻り型は、
ConcurrentHashMap
クラスのインスタンスまたはそのサブクラスでなければなりません。
グループ化操作を同時に実行するには、ストリームは並列である必要があります。
ConcurrentMap<BlogPostType, List<BlogPost>> postsPerType = posts.parallelStream()
.collect(groupingByConcurrent(BlogPost::getType));
Map
サプライヤ関数を
groupingByConcurrent
コレクタに渡すことを選択した場合は、その関数が
ConcurrentHashMap
またはそのサブクラスを返すことを確認する必要があります。
4 Java 9の追加機能
Java 9は
groupingBy
でうまく動作する2つの新しいコレクターをもたらしました – これに関するより多くの情報はリンクを見つけることができます/java9-stream-collectors[ここ]。
5結論
この記事では、Java 8
Collectors
APIによって提供される
groupingBy
コレクターの使用例をいくつか見てきました。
groupingBy
を使用して、要素のストリームの1つに基づいて要素のストリームを分類する方法と、分類結果をさらに収集、変更し、最終コンテナに整理する方法を説明しました。
この記事の例の完全な実装はhttps://github.com/eugenp/tutorials/tree/master/core-java-8[the GitHub project]にあります。