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