1. Введение

В этом кратком руководстве мы покажем, как объединить две карты, используя возможности Java 8.

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

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

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

Класс Employee выглядит следующим образом:

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

Затем мы можем передать некоторые данные в экземпляры Map:

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

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

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

3. Map.merge()

Java 8 добавляет новую функцию merge() в интерфейс java.util.Map.

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

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

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

Затем давайте введем функцию merge() вместе с правилом слияния:

Map<String, Employee> map3 = new HashMap<>(map1);

Наконец, мы перебираем карту2 и объединяем записи в карту3:

map3.merge(key, value, (v1, v2) -> new Employee(v1.getId(),v2.getName())

Давайте запустим программу и распечатаем содержимое карты3:

map2.forEach(
  (key, value) -> map3.merge(key, value, (v1, v2) -> new Employee(v1.getId(),v2.getName())));

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

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

Кроме того, мы замечаем, что объект Employee последней записи имеет идентификатор из карты1, а значение выбирается из карты2.

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

4. Stream.concat()

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

Stream API в Java 8 также может обеспечить простое решение нашей проблемы. Во-первых, нам нужно объединить наши экземпляры карты в один поток. Это именно то, что делает операция Stream.concat():

Здесь мы передаем наборы записей карты в качестве параметров. Далее нам нужно собрать наш результат в новую карту. Для этого мы можем использовать Collectors.toMap():

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

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

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

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

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

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

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

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

Map<String, 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())));

Как мы видим, повторяющиеся записи с ключом «Генри» были объединены в новая пара ключ-значение, в которой идентификатор нового сотрудника был выбран из карты2, а значение — из карты1.

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

5. Stream.of()

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

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

Сначала преобразуем map1 и map2 в один поток. Далее мы конвертируем поток в карту. Как мы видим, последний аргумент toMap() — это функция слияния. Он решает проблему дублирования ключей, выбирая поле id из записи v1 и имя из v2.

Map<String, 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())));

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

6. Простая потоковая передача

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

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

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

Map<String, 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)));

7. StreamEx

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

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

Проще говоря, StreamEx является усовершенствованием Stream API и предоставляет множество дополнительных полезных методов. Мы будем использовать экземпляр EntryStream для работы с парами ключ-значение:

«

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

«Идея состоит в том, чтобы объединить потоки наших карт в один. Затем мы собираем записи в новый экземпляр map3. Важно отметить, что выражение (e1, e2) -\u003e 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. В частности, мы использовали Map.merge(), Stream API , библиотека StreamEx.

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

«