J'ai quelques cours comme ci-dessous
class Pojo {
List<Item> items;
}
class Item {
T key1;
List<SubItem> subItems;
}
class SubItem {
V key2;
Object otherAttribute1;
}
Je souhaite agréger les éléments en fonction de key1
et pour chaque agrégation, les sous-éléments doivent être agrégés par key2
de la manière suivante:
Map<T, Map<V, List<Subitem>>
Comment est-ce possible avec Java 8 Collectors.groupingBy
imbrication?
J'essayais quelque chose et je suis resté à mi-chemin
pojo.getItems()
.stream()
.collect(
Collectors.groupingBy(Item::getKey1, /* How to group by here SubItem::getKey2*/)
);
Remarque: Ce n'est pas la même chose que groupingBy
en cascade qui fait une agrégation à plusieurs niveaux basée sur des champs dans le même objet comme indiqué ici
Vous ne pouvez pas grouper un seul élément par plusieurs clés, sauf si vous acceptez que l'élément puisse apparaître dans plusieurs groupes. Dans ce cas, vous souhaitez effectuer une sorte d'opération flatMap
.
Une façon d'y parvenir consiste à utiliser Stream.flatMap
avec une paire temporaire contenant les combinaisons de Item
et SubItem
avant la collecte. En raison de l'absence d'un type de paire standard, une solution typique consiste à utiliser Map.Entry
pour ça:
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))));
Une alternative ne nécessitant pas ces objets temporaires serait d'effectuer l'opération flatMap
directement dans le collecteur, mais malheureusement, flatMapping
ne sera pas disponible avant Java 9.
Avec cela, la solution ressemblerait à
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))));
et si nous ne voulons pas attendre Java 9 pour cela, nous pouvons ajouter un collecteur similaire à notre base de code, car ce n'est pas si difficile à implémenter:
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]));
}