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 AP​​Iを使用すると、宣言された方法でデータのコレクションを処理できます。

静的ファクトリメソッド 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]にあります。