以下のようないくつかのクラスがあります
class Pojo {
List<Item> items;
}
class Item {
T key1;
List<SubItem> subItems;
}
class SubItem {
V key2;
Object otherAttribute1;
}
key1
に基づいてアイテムを集計したいのですが、集計ごとに、次の方法でkey2
によってサブアイテムを集計する必要があります。
Map<T, Map<V, List<Subitem>>
Java 8 Collectors.groupingBy
入れ子でこれをどのように行うことができますか?
私は何かを試していて、途中で立ち往生しました
pojo.getItems()
.stream()
.collect(
Collectors.groupingBy(Item::getKey1, /* How to group by here SubItem::getKey2*/)
);
注:これは、カスケードされたgroupingBy
とは異なります。これは、説明したように、同じオブジェクト内のフィールドに基づいてマルチレベルの集約を行います here
1つのアイテムを複数のキーでグループ化することはできません。ただし、アイテムが複数のグループに表示される可能性がある場合を除きます。その場合、一種のflatMap
操作を実行する必要があります。
これを達成する1つの方法は、収集する前にItem
とSubItem
の組み合わせを保持する一時的なペアでStream.flatMap
を使用することです。標準のペアタイプがないため、一般的な解決策はMap.Entry
を使用することです。
Map<T, Map<V, List<SubItem>>> result = pojo.getItems().stream()
.flatMap(item -> item.subItems.stream()
.map(sub -> new AbstractMap.SimpleImmutableEntry<>(item, sub)))
.collect(Collectors.groupingBy(e -> e.getKey().getKey1(),
Collectors.mapping(Map.Entry::getValue,
Collectors.groupingBy(SubItem::getKey2))));
これらの一時オブジェクトを必要としない別の方法は、コレクターでflatMap
操作を実行することですが、残念ながら flatMapping
はJava 9まで存在しません。
これで、ソリューションは次のようになります
Map<T, Map<V, List<SubItem>>> result = pojo.getItems().stream()
.collect(Collectors.groupingBy(Item::getKey1,
Collectors.flatMapping(item -> item.getSubItems().stream(),
Collectors.groupingBy(SubItem::getKey2))));
また、Java 9を待つ必要がない場合は、実装がそれほど難しくないため、コードベースに同様のコレクターを追加できます。
static <T,U,A,R> Collector<T,?,R> flatMapping(
Function<? super T,? extends Stream<? extends U>> mapper,
Collector<? super U,A,R> downstream) {
BiConsumer<A, ? super U> acc = downstream.accumulator();
return Collector.of(downstream.supplier(),
(a, t) -> { try(Stream<? extends U> s=mapper.apply(t)) {
if(s!=null) s.forEachOrdered(u -> acc.accept(a, u));
}},
downstream.combiner(), downstream.finisher(),
downstream.characteristics().toArray(new Collector.Characteristics[0]));
}