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な考慮も必要で地味に大変でした。
もうちょっと邪道コーディングに早く出会えていればよかった…。