Слияние двух карт с Java 8
В этом кратком руководстве мы покажем, как объединить две карты, используя возможности Java 8 .
Чтобы быть более конкретным, мы рассмотрим различные сценарии слияния, включая карты с повторяющимися записями.
2. Инициализация
Для начала давайте определим два экземпляра карты :
private static MapString, Employee> map1 = new HashMap>(); private static MapString, Employee> map2 = new HashMap>();
Класс Сотрудник выглядит следующим образом:
public class Employee private Long id; private String name; // constructor, getters, setters >
Затем мы можем передать некоторые данные в экземпляры карты :
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);
Обратите внимание, что у нас есть идентичные ключи для записей employee1 и employee5 в наших картах, которые мы будем использовать позже.
3. Карта.слияние()
Java 8 добавляет новую функцию merge() в интерфейс java.util.Map .
Вот как работает функция merge() : если указанный ключ еще не связан со значением или значение равно null, он связывает ключ с заданным значением.
В противном случае он заменяет значение результатами данной функции переназначения. Если результат функции переназначения равен нулю, он удаляет результат.
Во-первых, давайте создадим новый HashMap , скопировав все записи из map1 :
MapString, Employee> map3 = new HashMap>(map1);
Далее давайте представим функцию merge() вместе с правилом слияния:
map3.merge(key, value, (v1, v2) -> new Employee(v1.getId(),v2.getName())
Наконец, мы пройдемся по map2 и объединим записи в map3 :
map2.forEach( (key, value) -> map3.merge(key, value, (v1, v2) -> new Employee(v1.getId(),v2.getName())));
Давайте запустим программу и распечатаем содержимое map3 :
John=Employee Annie=Employee George=Employee Henry=Employee
В результате наша объединенная карта имеет все элементы предыдущих записей HashMap . Записи с повторяющимися ключами были объединены в одну запись .
Кроме того, мы замечаем, что объект Employee последней записи имеет идентификатор из map1 , а значение выбирается из map2 .
Это из-за правила, которое мы определили в нашей функции слияния:
(v1, v2) -> new Employee(v1.getId(), v2.getName())
4. Поток.concat()
Stream API в Java 8 также может предоставить простое решение нашей проблемы. Во- первых, нам нужно объединить наши экземпляры Map в один Stream . Это именно то, что делает операция Stream.concat() :
Stream combined = Stream.concat(map1.entrySet().stream(), map2.entrySet().stream());
Здесь мы передаем наборы записей карты в качестве параметров. Далее нам нужно собрать наш результат в новую карту . Для этого мы можем использовать Collectors.toMap() :
MapString, Employee> result = combined.collect( Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
В результате сборщик будет использовать существующие ключи и значения наших карт. Но это решение далеко от совершенства. Как только наш сборщик встретит записи с повторяющимися ключами, он выдаст исключение IllegalStateException .
Чтобы справиться с этой проблемой, мы просто добавляем в наш сборщик третий лямбда-параметр «объединения»:
(value1, value2) -> new Employee(value2.getId(), value1.getName())
Он будет использовать лямбда-выражение каждый раз, когда будет обнаружен дубликат ключа.
Наконец, собираем все вместе:
MapString, Employee> 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())));
Наконец, давайте запустим код и посмотрим на результаты:
George=Employee John=Employee Annie=Employee Henry=Employee
Как мы видим, повторяющиеся записи с ключом «Генри» были объединены в новую пару «ключ-значение», где идентификатор нового сотрудника был выбран из карты2, а значение — из карты1 .
5. Поток()
Чтобы продолжать использовать Stream API, мы можем превратить наши экземпляры Map в единый поток с помощью Stream.of() .
Здесь нам не нужно создавать дополнительную коллекцию для работы с потоками:
MapString, Employee> 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())));
Сначала мы преобразуем map1 и map2 в один поток . Далее мы конвертируем поток в карту. Как мы видим, последний аргумент toMap() — это функция слияния. Он решает проблему дублирующихся ключей, выбирая поле id из записи v1 и имя из v2 .
Распечатанный экземпляр map3 после запуска программы:
George=Employeeid=2, name='George'> John=Employeeid=8, name='John'> Annie=Employeeid=22, name='Annie'> Henry=Employeeid=1, name='Henry'>
6. Простое потоковое вещание
Кроме того, мы можем использовать конвейер stream() для сборки наших записей карты. Фрагмент кода ниже демонстрирует, как добавить записи из map2 и map1 , игнорируя повторяющиеся записи:
MapString, Employee> map3 = map2.entrySet() .stream() .collect(Collectors.toMap( Map.Entry::getKey, Map.Entry::getValue, (v1, v2) -> new Employee(v1.getId(), v2.getName()), () -> new HashMap>(map1)));
Как мы ожидаем, результаты после слияния:
, Annie=Employee, George=Employee, Henry=Employee>
7. СтримЭкс
Помимо решений, предоставляемых JDK, мы также можем использовать популярную библиотеку StreamEx .
Проще говоря, StreamEx является усовершенствованием Stream API и предоставляет множество дополнительных полезных методов. Мы будем использовать экземпляр EntryStream для работы с парами ключ-значение :
MapString, Employee> map3 = EntryStream.of(map1) .append(EntryStream.of(map2)) .toMap((e1, e2) -> e1);
Идея состоит в том, чтобы объединить потоки наших карт в один. Затем мы собираем записи в новый экземпляр map3 . Важно отметить, что выражение (e1, e2) -> e1 помогает определить правило для работы с повторяющимися ключами. Без него наш код выдаст исключение IllegalStateException .
, John=Employee, Annie=Employee, Henry=Employee>
8. Резюме
В этой короткой статье мы узнали о различных способах слияния карт в Java 8. В частности, мы использовали Map.merge(), Stream API, библиотеку StreamEx .
Как всегда, код, использованный во время обсуждения, можно найти на GitHub .
How to merge two maps in Java
There are multiple ways to merge or join two instances of the HashMap class in Java. In this article, you’ll learn to join maps with and without handling duplicate keys.
Let us say you have got the following two HashMap objects that you want to combine:
MapString, Integer> map1 = new HashMap>(); map1.put("A", 1); map1.put("B", 2); map1.put("C", 3); map1.put("F", 4); MapString, Integer> map2 = new HashMap>(); map2.put("A", 3); map2.put("B", 4); map2.put("D", 5);
The Map.putAll() method provides a quick and simple solution to merge two maps. This method copies all key-value pairs from the second map to the first map. Since a HashMap object can not store duplicate keys, the Map.putAll() method override the value of duplicate keys in the first map with values from the second map.
// Merge second map with first map map1.putAll(map2); // Print new map System.out.println(map1);
The Map.merge() method was introduced in Java 8 and is useful for joining maps with duplicate keys. This method takes three arguments as input: key, value, and a remapping function to merge values for duplicate keys. If the specified key is not already associated with a value or is associated with null , the Map.merge() method associates it with the given non-null value. Otherwise, the Map.merge() method replaces the value with the results of the given remapping function. If the result of the remapping function is null , it removes the key altogether. The following example demonstrates how you can combine the values of duplicate keys using the remapping function of Map.merge() :
// Merge second map with first map map2.forEach((key, value) -> map1.merge(key, value, (v1, v2) -> v1 + v2) ); // Print new map System.out.println(map1); //
Notice the values of key B . It had value 2 in map1 and value 4 in map2 . After the merge, it has a combined value of 6 in the merged map. The remapping function allows you to write any merge logic that suits your needs.
The Stream.concat() method from the Stream API in Java 8 can also be used to combine two maps. As the name suggests, the Stream.concat() method combines the Map instances into one Stream object:
StreamMap.EntryString, Integer>> combined = Stream.concat(map1.entrySet().stream(), map2.entrySet().stream());
MapString, Integer> merged = combined.collect( Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
The above solution works fine as long as there are no duplicate keys. If it encounters any duplicate entry, it will throw an IllegalStateException exception. To handle duplicate entries, you can pass a merger function as a third parameter to the collector:
MapString, Integer> merged = combined.collect( Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (v1, v2) -> v1 + v2)); // Print new map System.out.println(merged); //
Stream.of() is another method from the Stream API that can be used to merge two maps in Java 9 and above:
// Merge the second map with the first map MapString, Integer> merged = Stream.of(map1, map2) .flatMap(map -> map.entrySet().stream()) .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (v1, v2) -> v1 + v2)); // Print new map System.out.println(merged); //
In the above example, we first transform map1 and map2 into a unified stream with the help of Stream.of() and Stream.flatMap() methods. Next, we convert the stream into a map using a collector function. ✌️ Like this article? Follow me on Twitter and LinkedIn. You can also subscribe to RSS Feed.
You might also like.
How to Join or Merge Two Maps in Java
Learn merging two hashmaps in both cases – ignoring duplicate keys (overwrites the value) or handling duplicate keys.
1. Merge Two HashMaps Ignoring Duplicate Keys
This one is a simple solution. Use firstMap.putAll(secondMap) method that copies all of the mappings from the secondMap to firstMap.
As we know hashmap does not allow duplicate keys. So when we merge the maps in this way, for duplicate keys in firstMap the value is overwritten by the value for the same key in secondMap .
Let’s take an example. In the following example, both maps have an entry with key “4”. After merging, we have the entry from the second map in the final map.
//map 1 HashMap firstMap = new HashMap<>(); firstMap.put(1, "A"); firstMap.put(2, "B"); firstMap.put(3, "C"); firstMap.put(4, "D"); //map 2 HashMap secondMap = new HashMap<>(); secondMap.put(4, "F"); //It will replace D with F secondMap.put(5, "G"); //A new pair to be added //Merge maps firstMap.putAll(secondMap); System.out.println(firstMap);
2. Merge Two Maps by Combining Values for Duplicate Keys
If we want to handle the cases where duplicate keys are present in the maps and we do not want to lose the data for any map and for any key. In this case, we can take the help of Map.merge() function added in Java 8.
The merge() function takes 3 arguments: key, value and a user-provided BiFunction to merge values for duplicate keys.
In our example, we want to append the values (from both maps) for a duplicate key “4”.
//map 1 HashMap firstMap = new HashMap<>(); firstMap.put(1, "A"); firstMap.put(2, "B"); firstMap.put(3, "C"); firstMap.put(4, "D"); //map 2 HashMap secondMap = new HashMap<>(); secondMap.put(4, "F"); //It will replace D with F secondMap.put(5, "G"); //A new pair to be added //Merge maps secondMap.forEach((key, value) -> firstMap.merge(key, value, String::concat)); System.out.println(firstMap);
Notice the value of key «4» . It had value D in the first map and F in the second map. In the merged map, it is a combined value as «DF» .
We can write any sort of merge logic in provided BiFunction . For example, if we want to append the values and put a delimiter in between then we can write our own BiFunction
secondMap.forEach( (key, value) -> firstMap.merge(key, value, (v1, v2) -> v1.equalsIgnoreCase(v2) ? v1 : v1 + "," + v2) );
Now the output is a concatenated value and a delimiter in between the values D and F.