1. Introducción
En este tutorial rápido, demostraremos cómo fusionar dos mapas utilizando las capacidades de Java 8 .
Para ser más específicos, examinaremos diferentes escenarios de fusión, incluidos mapas que tienen entradas duplicadas.
2. Inicialización
Para empezar, definamos dos instancias de Map :
private static Map map1 = new HashMap(); private static Map map2 = new HashMap();
La clase de empleado se ve así:
public class Employee { private Long id; private String name; // constructor, getters, setters }
Luego, podemos enviar algunos datos a las instancias de Map :
Employee employee1 = new Employee(1L, "Henry"); map1.put(employee1.getName(), employee1); Employee employee2 = new Employee(22L, "Annie"); map1.put(employee2.getName(), employee2); Employee employee3 = new Employee(8L, "John"); map1.put(employee3.getName(), employee3); Employee employee4 = new Employee(2L, "George"); map2.put(employee4.getName(), employee4); Employee employee5 = new Employee(3L, "Henry"); map2.put(employee5.getName(), employee5);
Tenga en cuenta que tenemos claves idénticas para las entradas employee1 y employee5 en nuestros mapas que usaremos más adelante.
3. Map.merge ()
Java 8 agrega una nueva función merge () en la interfaz java.util.Map .
Así es como funciona la función merge () : Si la clave especificada aún no está asociada con un valor o el valor es nulo, asocia la clave con el valor dado.
De lo contrario, reemplaza el valor con los resultados de la función de reasignación dada. Si el resultado de la función de reasignación es nulo, elimina el resultado.
Primero, construyamos un nuevo HashMap copiando todas las entradas del map1 :
Map map3 = new HashMap(map1);
A continuación, introduzcamos la función merge () junto con la regla de fusión:
map3.merge(key, value, (v1, v2) -> new Employee(v1.getId(),v2.getName())
Finalmente, iteraremos sobre map2 y fusionaremos las entradas en map3 :
map2.forEach( (key, value) -> map3.merge(key, value, (v1, v2) -> new Employee(v1.getId(),v2.getName())));
Ejecutemos el programa e imprimamos el contenido de map3 :
John=Employee{id=8, name="John"} Annie=Employee{id=22, name="Annie"} George=Employee{id=2, name="George"} Henry=Employee{id=1, name="Henry"}
Como resultado, nuestro mapa combinado tiene todos los elementos de las entradas anteriores de HashMap . Las entradas con claves duplicadas se han combinado en una sola entrada .
Además, notamos que el objeto Empleado de la última entrada tiene la identificación del mapa1 y el valor se elige del mapa2 .
Esto se debe a la regla que definimos en nuestra función de fusión:
(v1, v2) -> new Employee(v1.getId(), v2.getName())
4. Stream.concat ()
La API Stream en Java 8 también puede proporcionar una solución fácil a nuestro problema. Primero, necesitamos combinar nuestras instancias de Map en una secuencia . Eso es exactamente lo que hace la operación Stream.concat () :
Stream combined = Stream.concat(map1.entrySet().stream(), map2.entrySet().stream());
Aquí pasamos los conjuntos de entradas del mapa como parámetros. A continuación, debemos recopilar nuestro resultado en un nuevo mapa . Para eso podemos usar Collectors.toMap () :
Map result = combined.collect( Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
Como resultado, el recopilador utilizará las claves y valores existentes de nuestros mapas. Pero esta solución está lejos de ser perfecta. Tan pronto como nuestro recopilador encuentre entradas con claves duplicadas, lanzará una IllegalStateException .
Para manejar este problema, simplemente agregamos un tercer parámetro lambda de "fusión" en nuestro colector:
(value1, value2) -> new Employee(value2.getId(), value1.getName())
Utilizará la expresión lambda cada vez que se detecte una clave duplicada.
Finalmente, poniendo todo junto:
Map result = Stream.concat(map1.entrySet().stream(), map2.entrySet().stream()) .collect(Collectors.toMap( Map.Entry::getKey, Map.Entry::getValue, (value1, value2) -> new Employee(value2.getId(), value1.getName())));
Finalmente, ejecutemos el código y veamos los resultados:
George=Employee{id=2, name="George"} John=Employee{id=8, name="John"} Annie=Employee{id=22, name="Annie"} Henry=Employee{id=3, name="Henry"}
Como vemos, las entradas duplicadas con la clave "Henry" se fusionaron en un nuevo par clave-valor donde la identificación del nuevo Empleado se seleccionó del mapa2 y el valor del mapa1 .
5. Secuencia. De ()
Para continuar usando la API de Stream , podemos convertir nuestras instancias de Map en un flujo unificado con la ayuda de Stream.of () .
Aquí no tenemos que crear una colección adicional para trabajar con las transmisiones:
Map map3 = Stream.of(map1, map2) .flatMap(map -> map.entrySet().stream()) .collect(Collectors.toMap( Map.Entry::getKey, Map.Entry::getValue, (v1, v2) -> new Employee(v1.getId(), v2.getName())));
Primero, transformamos map1 y map2 en una sola secuencia . A continuación, convertimos la secuencia en el mapa. Como podemos ver, el último argumento de toMap () es una función de fusión. Resuelve el problema de las claves duplicadas seleccionando el campo id de la entrada v1 y el nombre de la v2 .
La instancia de map3 impresa después de ejecutar el programa:
George=Employee{id=2, name="George"} John=Employee{id=8, name="John"} Annie=Employee{id=22, name="Annie"} Henry=Employee{id=1, name="Henry"}
6. Transmisión simple
Además, podemos usar una canalización stream () para ensamblar nuestras entradas de mapa. El fragmento de código siguiente demuestra cómo agregar las entradas de map2 y map1 ignorando las entradas duplicadas:
Map map3 = map2.entrySet() .stream() .collect(Collectors.toMap( Map.Entry::getKey, Map.Entry::getValue, (v1, v2) -> new Employee(v1.getId(), v2.getName()), () -> new HashMap(map1)));
Como esperamos, los resultados después de la fusión son:
{John=Employee{id=8, name="John"}, Annie=Employee{id=22, name="Annie"}, George=Employee{id=2, name="George"}, Henry=Employee{id=1, name="Henry"}}
7. StreamEx
In addition to solutions that are provided by the JDK, we can also use the popular StreamEx library.
Simply put, StreamEx is an enhancement for the Stream API and provides many additional useful methods. We'll use an EntryStream instance to operate on key-value pairs:
Map map3 = EntryStream.of(map1) .append(EntryStream.of(map2)) .toMap((e1, e2) -> e1);
The idea is to merge the streams of our maps into one. Then we collect the entries into the new map3 instance. Important to mention, the (e1, e2) -> e1 expression as it helps to define the rule for dealing with the duplicate keys. Without it, our code will throw an IllegalStateException.
And now, the results:
{George=Employee{id=2, name="George"}, John=Employee{id=8, name="John"}, Annie=Employee{id=22, name="Annie"}, Henry=Employee{id=1, name="Henry"}}
8. Summary
En este breve artículo, aprendimos diferentes formas de fusionar mapas en Java 8. Más específicamente, usamos Map.merge (), Stream API, biblioteca StreamEx .
Como siempre, el código utilizado durante la discusión se puede encontrar en GitHub.