Grupo Java 8 anidado (varios niveles) por

Resuelto Sandesh Kumar asked hace 8 años • 1 respuestas

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 key1y para cada agregación, los subelementos deben agregarse de key2la siguiente manera:

Map<T, Map<V, List<Subitem>>

¿ Cómo es esto posible con Collectors.groupingByel 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í.

Sandesh Kumar avatar Aug 25 '16 01:08 Sandesh Kumar
Aceptado

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 flatMapoperación.

Una forma de lograr esto es usarlo Stream.flatMapcon un par temporal que contenga las combinaciones de Itemy SubItemantes de recolectar. Debido a la ausencia de un tipo de par estándar, una solución típica es utilizar Map.Entrypara 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 flatMapoperación directamente en el recopilador, pero desafortunadamente flatMappingno 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]));
}
Holger avatar Aug 24 '2016 18:08 Holger