Перейти к основному содержимому

Преобразование списка в карту с пользовательским поставщиком

· 4 мин. чтения

1. Обзор

В этом уроке мы собираемся преобразовать List<E> в Map<K, List<E>> . Мы добьемся этого с помощью Stream API Java и функционального интерфейса Supplier .

2. Поставщик в JDK 8

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

Кроме того, Поставщик может выполнять ленивую генерацию значений .

3. Преобразование списка в карту

Stream API обеспечивает поддержку манипулирования списками. Одним из таких примеров является метод Stream #collect . Однако в методах Stream API нет способа напрямую передать Поставщики параметрам нижестоящего потока.

В этом руководстве мы рассмотрим методы Collectors.groupingBy , Collectors.toMap и Stream.collect с примерами фрагментов кода. Мы сосредоточимся на методах, которые позволяют нам использовать пользовательский Supplier .

В этом руководстве мы обработаем коллекции String List в следующих примерах:

List source = Arrays.asList("List", "Map", "Set", "Tree");

Мы объединим приведенный выше список в карту, ключом которой является длина строки. Когда мы закончим, у нас будет карта, которая выглядит так:

{
3: ["Map", "Set"],
4: ["List", "Tree"]
}

3.1. Коллекторы.groupingBy()

С помощью Collectors.groupingBy мы можем преобразовать коллекцию в карту с определенным классификатором. Классификатор — это атрибут элемента, мы будем использовать этот атрибут для включения элементов в разные группы:

public Map<Integer, List> groupingByStringLength(List source, 
Supplier<Map<Integer, List>> mapSupplier,
Supplier<List> listSupplier) {
return source.stream()
.collect(Collectors.groupingBy(String::length, mapSupplier, Collectors.toCollection(listSupplier)));
}

Мы можем подтвердить, что он работает с:

Map<Integer, List> convertedMap = converter.groupingByStringLength(source, HashMap::new, ArrayList::new);
assertTrue(convertedMap.get(3).contains("Map"));

3.2. Коллекторы.toMap()

Метод Collectors.toMap преобразует элементы потока в карту.

Начнем с определения метода с исходной строкой и поставщиков List и Map :

public Map<Integer, List> collectorToMapByStringLength(List source, 
Supplier<Map<Integer, List>> mapSupplier,
Supplier<List> listSupplier)

Затем мы определяем, как получить ключ и значение из элемента. Для этого мы используем две новые функции:

Function<String, Integer> keyMapper = String::length;

Function<String, List> valueMapper = (element) -> {
List collection = listSupplier.get();
collection.add(element);
return collection;
};

Наконец, мы определяем функцию, которая вызывается при конфликте клавиш. В этом случае мы хотим объединить содержимое обоих:

BinaryOperator<List> mergeFunction = (existing, replacement) -> {
existing.addAll(replacement);
return existing;
};

Складывая все вместе, получаем:

source.stream().collect(Collectors.toMap(keyMapper, valueMapper, mergeFunction, mapSupplier))

Обратите внимание, что в большинстве случаев функции, которые мы определяем, являются анонимными встроенными функциями внутри списка аргументов метода.

Давайте проверим это:

Map<Integer, List> convertedMap = converter.collectorToMapByStringLength(source, HashMap::new, ArrayList::new);
assertTrue(convertedMap.get(3).contains("Map"));

3.3. Поток.собирать()

Метод Stream.collect можно использовать для преобразования элементов потока в коллекцию любого типа.

Для этого нам также нужно определить метод с поставщиками списка и карты , который будет вызываться, когда потребуется новая коллекция:

public Map<Integer, List> streamCollectByStringLength(List source, 
Supplier<Map<Integer, List>> mapSupplier,
Supplier<List> listSupplier)

Затем мы переходим к определению аккумулятора , который по ключу элемента получает существующий список или создает новый и добавляет элемент в ответ:

BiConsumer<Map<Integer, List>, String> accumulator = (response, element) -> {
Integer key = element.length();
List values = response.getOrDefault(key, listSupplier.get());
values.add(element);
response.put(key, values);
};

Наконец, мы переходим к объединению значений, сгенерированных функцией-аккумулятором:

BiConsumer<Map<Integer, List>, Map<Integer, List>> combiner = (res1, res2) -> {
res1.putAll(res2);
};

Собрав все вместе, мы просто вызываем метод collect для потока наших элементов:

source.stream().collect(mapSupplier, accumulator, combiner);

Обратите внимание, что в большинстве случаев функции, которые мы определяем, являются анонимными встроенными функциями внутри списка аргументов метода.

Результат теста будет таким же, как и у двух других методов:

Map<Integer, List> convertedMap = converter.streamCollectByStringLength(source, HashMap::new, ArrayList::new);
assertTrue(convertedMap.get(3).contains("Map"));

4. Вывод

В этом руководстве мы показали, как преобразовать List<E> в Map<K, List<E>> с помощью Stream API Java 8 с настраиваемыми Suppliers .

Полный исходный код с примерами из этого руководства можно найти на GitHub .