Recopile resultados de una operación de mapa en un Mapa usando Collectors.toMap o groupingBy

8

Tengo una lista de tipos List<A>y con la operación de mapa obtengo una lista colectiva de tipos List<B>para todos los elementos A fusionados en una lista.

List<A> listofA = [A1, A2, A3, A4, A5, ...]

List<B> listofB = listofA.stream()
  .map(a -> repo.getListofB(a))
  .flatMap(Collection::stream)
  .collect(Collectors.toList());

sin plano

List<List<B>> listOflistofB = listofA.stream()
  .map(a -> repo.getListofB(a))
  .collect(Collectors.toList());

Quiero recopilar los resultados como un mapa de tipo Map<A, List<B>>y hasta ahora intentar con varias opciones Collectors.toMapu Collectors.groupingByopciones, pero no puedo obtener el resultado deseado.

Amitoj
fuente
2
¿Tienes Aelementos repetidos en tu List<A>?
Federico Peralta Schaffner

Respuestas:

8

Puede usar el toMaprecopilador con una referencia de método acotado para obtener lo que necesita. Observe también que esta solución asume que no tiene instancias A repetidas en su contenedor fuente. Si esa precondición mantiene esta solución, obtendría el resultado deseado. Así es como se ve.

Map<A, Collection<B>> resultMap = listofA.stream()
    .collect(Collectors.toMap(Function.identity(), repo::getListofB);

Si tiene elementos A duplicados, debe usar esta función de combinación además de lo que se proporciona anteriormente. La función de fusión se ocupa de conflictos clave, si los hay.

Map<A, Collection<B>> resultMap = listofA.stream()
       .collect(Collectors.toMap(Function.identity(), repo::getListofB, 
            (a, b) -> {
                a.addAll(b);
                return a;
        }));

Y aquí hay un enfoque Java9 mucho más sucinto que utiliza el flatMappingrecopilador para manejar elementos A repetidos.

Map<A, List<B>> aToBmap = listofA.stream()
        .collect(Collectors.groupingBy(Function.identity(),
                Collectors.flatMapping(a -> getListofB(a).stream(), 
                        Collectors.toList())));
Ravindra Ranwala
fuente
2
¡Hola, qué bueno que hayas agregado cómo manejar duplicados! Estaba agregando una respuesta con eso, lo dejaré porque creo que complementa la tuya, ¡salud!
Federico Peralta Schaffner
2
Sí, tu comentario fue útil. Buena atrapada !
Ravindra Ranwala
3

Sería sencillo

listofA.stream().collect(toMap(Function.identity(), a -> getListofB(a)));
Code_Mode
fuente
2

Para recopilar un Mapdonde las claves son los Aobjetos sin cambios, y los valores son la lista de Bobjetos correspondientes , puede reemplazar el toList()recopilador por el siguiente recopilador:

toMap(Function.identity(), a -> repo.getListOfB(a))

El primer argumento define cómo calcular la clave del objeto original: identity()toma el objeto original de la secuencia sin cambios.

El segundo argumento define cómo se calcula el valor , por lo que aquí solo consiste en una llamada a su método que transforma Aa una lista de B.

Dado que el repométodo solo toma un parámetro, también puede mejorar la claridad reemplazando el lambda con una referencia de método:

toMap(Function.identity(), repo::getListOfB)
kgautron
fuente
Gracias @kgautron, tu respuesta es correcta y está claramente explicada. Acepté la otra respuesta porque la vi primero.
Amitoj
2

En esta respuesta, estoy mostrando lo que sucede si tienes Aelementos repetidos en tu List<A> listofAlista.

En realidad, si hubiera duplicados listofA, el siguiente código arrojaría un IllegalStateException:

Map<A, Collection<B>> resultMap = listofA.stream()
        .collect(Collectors.toMap(
                            Function.identity(), 
                            repo::getListofB);

La excepción podría ser lanzada porque Collectors.toMapno sabe cómo fusionar valores cuando hay una colisión en las teclas (es decir, cuando la función del mapeador de teclas devuelve duplicados, como sería el caso Function.identity()si hubiera elementos repetidos en la listofAlista).

Esto se afirma claramente en los documentos :

Si las claves asignadas contienen duplicados (de acuerdo con Object.equals(Object)), IllegalStateExceptionse emite una cuando se realiza la operación de recopilación. Si las claves asignadas pueden tener duplicados, use toMap(Function, Function, BinaryOperator) en su lugar.

Los documentos también nos dan la solución: en caso de que haya elementos repetidos, debemos proporcionar una forma de fusionar valores. Aquí hay una de esas formas:

Map<A, Collection<B>> resultMap = listofA.stream()
        .collect(Collectors.toMap(
                            Function.identity(), 
                            a -> new ArrayList<>(repo.getListofB(a)),
                            (left, right) -> {
                                left.addAll(right);
                                return left;
                            });

Esto usa la versión sobrecargada Collectors.toMapque acepta una función de fusión como su tercer argumento. Dentro de la función de fusión, Collection.addAllse está utilizando para agregar los Belementos de cada Aelemento repetido en una lista única para cada uno A.

En la función del mapeador de valores, ArrayListse crea un nuevo , de modo que el original List<B>de cada uno Ano está mutado. Además, como estamos creando un Arraylist, sabemos de antemano que puede ser mutado (es decir, podemos agregarle elementos más adelante, en caso de que haya duplicados listofA).

Federico Peralta Schaffner
fuente