きり丸の技術日記

技術検証したり、資格等をここに残していきます。

Javaのラムダ式(Stream API)で複数キーでCollectors.groupingByする

JavaのStream APIにて、複数キーでCollectors.groupingByする方法を記載します。

環境

  • Java
    • 17

対応

前提として、次のrecordの項目を元に、Collectors.groupingByをします。

public record Book(String id, String language, int year, String salesTerritory, Author author) {}
public record Author(String name) {}

正道な方法

COllectors.groupingByの第二引数にCollectors.groupingByを渡すことができます。

これを元に、groupingしたいキーの数だけつなげます。

Collectors.groupingBy(第一キー, Collectors.groupingBy(第二キー...))

// 実際の使い方
Collectors.groupingBy(
  Book::language,
  Collectors.groupingBy(Book::year,
    Collectors.groupingBy(Book::author))
)

懸念点としてはgroupingしたいキーが増えれば増えるほど、返却値のネストが増えることです。groupingしたい優先度がある場合は回避できないので、頑張って読み解きましょう。

var list = List.of(
  new Book("1", "Japanese", 1990, "JP", new Author("きり丸")),
  new Book("2", "Japanese", 1990, "JP", new Author("乱太郎")),
  new Book("3", "Japanese", 1990, "JP", new Author("しんべえ")),
  new Book("4", "Japanese", 1990, "JP", new Author("きり丸"))
);

// 3つのキーでgroupingしているので、ネストが3つ
Map<String, Map<Integer, Map<Author, List<Book>>> group = 
  param.stream().collect(
    Collectors.groupingBy(Book::language,
        Collectors.groupingBy(Book::year,
            Collectors.groupingBy(Book::author))
    ));

邪道な方法

groupingしたいだけで、groupingされた値の順番は気にしない方法です。groupingに使用するキーをすべて結合しましょう。

// すべてStringにしているので、ネストが1つ
Map<String, List<Book>> group =
  param.stream().collect(
    Collectors.groupingBy(
      e -> e.language + e.year + e.author
    ));

また、別のメリットとしては、文字列として結合するため、nullが「"null"」という文字列に変換されることです。Collectors.groupingByはnullを許容しないため、すべてのgroupingするキーをNonNullにしておくか、こちらの記事のようにOptional型に変換する必要があります。

groupingの順番さえ気にならないのであれば、ネストが深くならない上にNullableで考慮すべき処理が減るので、一考の余地はあります。問題がありそうな場合は、指摘していただけると助かります。

ソースコード

終わりに

本業で5つのキーでgroupingする必要がありました。正道な方法でコーディングしていたのですが、ネストは深くなり、Nullableな考慮も必要で地味に大変でした。

もうちょっと邪道コーディングに早く出会えていればよかった…。

類似情報