Grupo Java 8 anidado (varios niveles) por
Tengo algunas clases como las de abajo.
class Pojo {
List<Item> items;
}
class Item {
T key1;
List<SubItem> subItems;
}
class SubItem {
V key2;
Object otherAttribute1;
}
Quiero agregar los elementos en función de key1
y para cada agregación, los subelementos deben agregarse de key2
la siguiente manera:
Map<T, Map<V, List<Subitem>>
¿ Cómo es esto posible con Collectors.groupingBy
el anidamiento de Java 8?
Estaba intentando algo y me quedé a mitad de camino.
pojo.getItems()
.stream()
.collect(
Collectors.groupingBy(Item::getKey1, /* How to group by here SubItem::getKey2*/)
);
Nota: Esto no es lo mismo que en cascada groupingBy
, que realiza una agregación multinivel basada en campos en el mismo objeto como se analiza aquí.
No puede agrupar un solo elemento mediante varias claves, a menos que acepte que el elemento aparezca potencialmente en varios grupos. En ese caso, desea realizar algún tipo de flatMap
operación.
Una forma de lograr esto es usarlo Stream.flatMap
con un par temporal que contenga las combinaciones de Item
y SubItem
antes de recolectar. Debido a la ausencia de un tipo de par estándar, una solución típica es utilizar Map.Entry
para ello:
Map<T, Map<V, List<SubItem>>> result = pojo.getItems().stream()
.flatMap(item -> item.subItems.stream()
.map(sub -> new AbstractMap.SimpleImmutableEntry<>(item.getKey1(), sub)))
.collect(Collectors.groupingBy(AbstractMap.SimpleImmutableEntry::getKey,
Collectors.mapping(Map.Entry::getValue,
Collectors.groupingBy(SubItem::getKey2))));
Una alternativa que no requiere estos objetos temporales sería realizar la flatMap
operación directamente en el recopilador, pero desafortunadamente flatMapping
no estará disponible hasta Java 9.
Con eso, la solución sería así.
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))));
y si no queremos esperar a Java 9 para eso, podemos agregar un recopilador similar a nuestro código base, ya que no es tan difícil de implementar:
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]));
}