Слияние двух карт с Java 8

Слияние двух карт с Java 8

 

1. Вступление

В этом кратком руководствеwe’ll demonstrate how to merge two maps using the Java 8 capabilities.

Чтобы быть более конкретным, мы рассмотрим различные сценарии объединения, включая карты с повторяющимися записями.

2. инициализация

Для начала определим два экземпляраMap:

private static Map map1 = new HashMap<>();
private static Map map2 = new HashMap<>();

КлассEmployee выглядит так:

public class Employee {
 
    private Long id;
    private String name;
 
    // constructor, getters, setters
}

Затем мы можем поместить некоторые данные в экземпляры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);

Обратите внимание, что у нас есть одинаковые ключи для записейemployee1 иemployee5 на наших картах, которые мы будем использовать позже.

3. Map.merge()с

Java 8 adds a new merge() function into the java.util.Map interface.

Вот как работает функцияmerge(): если указанный ключ еще не связан со значением или значение равно нулю, он связывает ключ с заданным значением.

В противном случае он заменяет значение результатами данной функции переотображения. Если результат функции переназначения равен нулю, он удаляет результат.

Во-первых, давайте создадим новыйHashMap, скопировав все записи изmap1:

Map 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{id=8, name='John'}
Annie=Employee{id=22, name='Annie'}
George=Employee{id=2, name='George'}
Henry=Employee{id=1, name='Henry'}

В результатеour combined Map has all the elements of the previous HashMap entries. Entries with duplicate keys have been merged into one entry.

Также мы заметили, что объектEmployee последней записи имеетid изmap1, а значение выбирается изmap2.

Это из-за правила, которое мы определили в нашей функции слияния:

(v1, v2) -> new Employee(v1.getId(), v2.getName())

4. Stream.concat()с

APIStream в Java 8 также может предоставить простое решение нашей проблемы. Во-первых,we need to combine our Map instances into one Stream. Именно это и делает операцияStream.concat():

Stream combined = Stream.concat(map1.entrySet().stream(), map2.entrySet().stream());

Здесь мы передаем наборы записей карты в качестве параметров. Next, we need to collect our result into a new Map. Для этого мы можем использоватьCollectors.toMap():

Map result = combined.collect(
  Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));

В результате сборщик будет использовать существующие ключи и значения наших карт. Но это решение далеко не идеально. Как только наш сборщик встречает записи с повторяющимися ключами, он выдастIllegalStateException.

Чтобы решить эту проблему, мы просто добавляем третий лямбда-параметр «слияния» в наш сборщик:

(value1, value2) -> new Employee(value2.getId(), value1.getName())

Он будет использовать лямбда-выражение каждый раз, когда обнаруживается повторяющийся ключ.

Наконец, собрав все воедино:

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())));

Наконец, давайте запустим код и посмотрим на результаты:

George=Employee{id=2, name='George'}
John=Employee{id=8, name='John'}
Annie=Employee{id=22, name='Annie'}
Henry=Employee{id=3, name='Henry'}

Как мы видим, повторяющиеся записи с ключом“Henry” были объединены в новую пару ключ-значение, гдеthe id of the new Employee was picked from the map2 and the value from map1.

5. Stream.of()

Чтобы продолжить использование APIStream, мы можем превратить наши экземплярыMap в единый поток с помощьюStream.of().

Здесь нам не нужно создавать дополнительную коллекцию для работы с потоками:

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())));

Во-первых,we transform map1 and map2 into a single stream. Далее мы конвертируем поток в карту. Как мы видим, последний аргументtoMap() - это функция слияния. Он решает проблему дублирования ключей, выбирая поле id из записиv1 и имя изv2.

Напечатанный экземплярmap3 после запуска программы:

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. Простая потоковая передача

Кроме того, мы можем использовать спайплайнstream() для сборки записей на карте. Приведенный ниже фрагмент кода демонстрирует, как добавить записи изmap2 иmap1, игнорируя повторяющиеся записи:

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)));

Как мы ожидаем, результаты после слияния:

{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с

В дополнение к решениям, предоставляемым JDK, мы также можем использовать популярную библиотекуStreamEx .

Проще говоря,StreamEx is an enhancement for the Stream API и предоставляет множество дополнительных полезных методов. Мы будем использоватьEntryStream instance to operate on key-value pairs:

Map map3 = EntryStream.of(map1)
  .append(EntryStream.of(map2))
  .toMap((e1, e2) -> e1);

Идея состоит в том, чтобы объединить потоки наших карт в одну. Затем мы собираем записи в новый экземплярmap3. Важно отметить, что выражение(e1, e2) → e1 помогает определить правило работы с повторяющимися ключами. Без него наш код выдастIllegalStateException.

А теперь результаты:

{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. Резюме

В этой короткой статье мы узнали о различных способах объединения карт в Java 8. В частности,we used Map.merge(), Stream API, StreamEx library.

Как всегда, код, использованный во время обсуждения, можно найтиover on GitHub.