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]にあります。