streams recorrer procesamiento parte lista ejemplo datos con collection java lambda functional-programming java-8 collectors

recorrer - Java 8 Streams: asigna el mismo objeto varias veces según diferentes propiedades



recorrer lista java 8 (1)

Un colega mío me presentó un problema interesante y no pude encontrar una solución bonita y bonita para Java 8. El problema es transmitir a través de una lista de POJOs y luego recopilarlos en un mapa basado en múltiples propiedades;

Imagina el siguiente POJO:

private static class Customer { public String first; public String last; public Customer(String first, String last) { this.first = first; this.last = last; } public String toString() { return "Customer(" + first + " " + last + ")"; } }

Configúralo como una List<Customer> :

// The list of customers List<Customer> customers = Arrays.asList( new Customer("Johnny", "Puma"), new Customer("Super", "Mac"));

Alternativa 1 : use un Map fuera de la "corriente" (o más bien fuera de forEach ).

// Alt 1: not pretty since the resulting map is "outside" of // the stream. If parallel streams are used it must be // ConcurrentHashMap Map<String, Customer> res1 = new HashMap<>(); customers.stream().forEach(c -> { res1.put(c.first, c); res1.put(c.last, c); });

Alternativa 2 : Cree entradas de mapa y flatMap , luego flatMap un flatMap ellas. En mi opinión, es un poco demasiado detallado y no es tan fácil de leer.

// Alt 2: A bit verbose and "new AbstractMap.SimpleEntry" feels as // a "hard" dependency to AbstractMap Map<String, Customer> res2 = customers.stream() .map(p -> { Map.Entry<String, Customer> firstEntry = new AbstractMap.SimpleEntry<>(p.first, p); Map.Entry<String, Customer> lastEntry = new AbstractMap.SimpleEntry<>(p.last, p); return Stream.of(firstEntry, lastEntry); }) .flatMap(Function.identity()) .collect(Collectors.toMap( Map.Entry::getKey, Map.Entry::getValue));

Alternativa 3 : Esta es otra que encontré con el código "más bonito" hasta el momento, pero utiliza la versión de reduce de tres argumentos y el tercer parámetro es un poco dudoso como se encuentra en esta pregunta: Propósito del tercer argumento para ''reducir ''Función en la programación funcional Java 8 . Además, reduce no parece ser un buen ajuste para este problema ya que está mutando y es posible que las corrientes paralelas no funcionen con el enfoque a continuación.

// Alt 3: using reduce. Not so pretty Map<String, Customer> res3 = customers.stream().reduce( new HashMap<>(), (m, p) -> { m.put(p.first, p); m.put(p.last, p); return m; }, (m1, m2) -> m2 /* <- NOT USED UNLESS PARALLEL */);

Si el código anterior se imprime así:

System.out.println(res1); System.out.println(res2); System.out.println(res3);

El resultado sería:

{Super = Cliente (Super Mac), Johnny = Cliente (Johnny Puma), Mac = Cliente (Super Mac), Puma = Cliente (Johnny Puma)}
{Super = Cliente (Super Mac), Johnny = Cliente (Johnny Puma), Mac = Cliente (Super Mac), Puma = Cliente (Johnny Puma)}
{Super = Cliente (Super Mac), Johnny = Cliente (Johnny Puma), Mac = Cliente (Super Mac), Puma = Cliente (Johnny Puma)}

Entonces, ahora a mi pregunta: ¿Cómo debo, de una manera ordenada de Java 8, recorrer la List<Customer> y luego recopilarla de alguna manera como un Map<String, Customer> donde se divide todo en dos teclas ( first AND last ) es decir, el Customer se asigna dos veces. No quiero usar ninguna biblioteca de terceros, no quiero usar un mapa fuera de la secuencia como en alt 1. ¿Hay alguna otra alternativa agradable?

El código completo se puede encontrar en el hastebin para copiar y pegar fácilmente para que todo funcione.


Creo que sus alternativas 2 y 3 pueden reescribirse para que sean más claras:

Alternativa 2 :

Map<String, Customer> res2 = customers.stream() .flatMap( c -> Stream.of(c.first, c.last) .map(k -> new AbstractMap.SimpleImmutableEntry<>(k, c)) ).collect(toMap(Map.Entry::getKey, Map.Entry::getValue));

Alternativa 3 : Sus abusos de código se reduce al mutar el HashMap. Para hacer la reducción mutable, utilice collect :

Map<String, Customer> res3 = customers.stream() .collect( HashMap::new, (m,c) -> {m.put(c.first, c); m.put(c.last, c);}, HashMap::putAll );

Tenga en cuenta que estos no son idénticos. La Alternativa 2 lanzará una excepción si hay claves duplicadas, mientras que la Alternativa 3 sobrescribirá las entradas en silencio.

Si lo que desea es sobrescribir las entradas en caso de claves duplicadas, personalmente preferiría la Alternativa 3. De inmediato me queda claro qué hace. Se asemeja más a la solución iterativa. Espero que sea más eficaz ya que la Alternativa 2 tiene que hacer un montón de asignaciones por cliente con todo ese mapeo plano.

Sin embargo, la Alternativa 2 tiene una gran ventaja sobre la Alternativa 3 al separar la producción de entradas de su agregación. Esto le da una gran flexibilidad. Por ejemplo, si desea cambiar la Alternativa 2 para sobrescribir las entradas en claves duplicadas en lugar de lanzar una excepción, simplemente agregaría (a,b) -> b a toMap(...) . Si decide que desea recopilar entradas coincidentes en una lista, todo lo que tendría que hacer es reemplazar toMap(...) con groupingBy(...) , etc.