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.
«