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 .