1. Введение
В этом кратком руководстве мы покажем, как объединить две карты, используя возможности Java 8 .
Чтобы быть более конкретным, мы рассмотрим различные сценарии слияния, включая карты с повторяющимися записями.
2. Инициализация
Для начала давайте определим два экземпляра карты :
private static Map<String, Employee> map1 = new HashMap<>();
private static Map<String, 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
:
Map<String, 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{id=8, name='John'}
Annie=Employee{id=22, name='Annie'}
George=Employee{id=2, name='George'}
Henry=Employee{id=1, name='Henry'}
В результате наша объединенная карта
имеет все элементы предыдущих записей 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()
:
Map<String, Employee> result = combined.collect(
Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
В результате сборщик будет использовать существующие ключи и значения наших карт. Но это решение далеко от совершенства. Как только наш сборщик встретит записи с повторяющимися ключами, он выдаст исключение IllegalStateException
.
Чтобы справиться с этой проблемой, мы просто добавляем в наш сборщик третий лямбда-параметр «объединения»:
(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())));
Наконец, давайте запустим код и посмотрим на результаты:
George=Employee{id=2, name='George'}
John=Employee{id=8, name='John'}
Annie=Employee{id=22, name='Annie'}
Henry=Employee{id=3, name='Henry'}
Как мы видим, повторяющиеся записи с ключом «Генри»
были объединены в новую пару «ключ-значение», где идентификатор нового сотрудника
был выбран из карты2,
а значение — из карты1
.
5. Поток()
Чтобы продолжать использовать Stream
API, мы можем превратить наши экземпляры Map
в единый поток с помощью Stream.of()
.
Здесь нам не нужно создавать дополнительную коллекцию для работы с потоками:
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())));
Сначала мы преобразуем map1
и map2
в один поток . Далее мы конвертируем поток в карту. Как мы видим, последний аргумент 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<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)));
Как мы ожидаем, результаты после слияния:
{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. СтримЭкс
Помимо решений, предоставляемых JDK, мы также можем использовать популярную библиотеку StreamEx
.
Проще говоря, StreamEx
является усовершенствованием Stream
API и предоставляет множество дополнительных полезных методов. Мы будем использовать экземпляр EntryStream
для работы с парами ключ-значение :
Map<String, Employee> 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. В частности, мы использовали Map.merge(), Stream API,
библиотеку StreamEx .
Как всегда, код, использованный во время обсуждения, можно найти на GitHub .