«1. Обзор
В этом уроке мы собираемся преобразовать List\u003cE\u003e в Map\u003cK, List\u003cE\u003e\u003e. Мы добьемся этого с помощью Java Stream API и функционального интерфейса Supplier.
2. Поставщик в JDK 8
Поставщик часто используется как фабрика. Метод может принимать Поставщика в качестве входных данных и ограничивать тип с помощью ограниченного подстановочного типа, после чего клиент может передать фабрику, которая создает любой подтип данного типа.
Кроме того, Поставщик может выполнять ленивую генерацию значений.
3. Преобразование списка в карту
Stream API поддерживает работу со списками. Одним из таких примеров является метод Stream#collect. Однако в методах Stream API нет способа напрямую передать Поставщики параметрам нижестоящего потока.
В этом руководстве мы рассмотрим методы Collectors.groupingBy, Collectors.toMap и Stream.collect с примерами фрагментов кода. Мы сосредоточимся на методах, которые позволяют нам использовать настраиваемый поставщик.
В этом руководстве мы обработаем коллекции String List в следующих примерах:
List source = Arrays.asList("List", "Map", "Set", "Tree");
Мы агрегируем приведенный выше список в карту, ключом которой является длина строки. Когда мы закончим, у нас будет карта, которая выглядит так:
{
3: ["Map", "Set"],
4: ["List", "Tree"]
}
3.1. Collectors.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. Collectors.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()
Метод Stream.collect можно использовать для уменьшения элементов потока в коллекцию любого типа.
Для этого нам также нужно определить метод с поставщиками List и Map, который будет вызываться, когда потребуется новая коллекция:
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\u003cE\u003e в Map\u003cK, List\u003c E\u003e\u003e с Java 8 Stream API с пользовательскими поставщиками.
Полный исходный код с примерами из этого руководства можно найти на GitHub.