Как преобразовать List <V> в Map <K, List <V>>, с потоками Java 8 и пользовательскими списками и поставщиками карт?

Легко преобразовать List<V> в Map<K, List<V>>. Например:

public Map<Integer, List<String>> getMap(List<String> strings) {
   return
      strings.stream()
             .collect(Collectors.groupingBy(String::length));
}

Но я хочу сделать это со своими List и Map поставщиками.

Я придумал это:

public Map<Integer, List<String>> getMap(List<String> strings) {
   return strings.stream()
       .collect(Collectors.toMap(
             String::length,
             item -> {List<String> list = new ArrayList<>(); list.add(item); return list;},
             (list1, list2) -> {list1.addAll(list2); return list1;},
             HashMap::new));
}

Вопрос: Есть ли более простой, менее подробный или более эффективный способ сделать это? Например, что-то вроде этого (что не работает):

return strings.stream()
      .collect(Collectors.toMap(
            String::length,
            ArrayList::new,                    
            HashMap::new));

И что, если мне нужно только определить поставщика List, но не поставщика Map?

Ответ 1

У вас может быть следующее:

public Map<Integer, List<String>> getMap(List<String> strings) {
    return strings.stream().collect(
      Collectors.groupingBy(String::length, HashMap::new, Collectors.toCollection(ArrayList::new))
    );
}

Коллектор groupingBy(classifier, mapFactory, downstream) может использоваться для указания того, какой тип карты требуется, передав ему поставщика желаемой карты для mapFactory. Затем сборщик нисходящего потока, который используется для сбора элементов, сгруппированных по одному и тому же ключу, toCollection(collectionFactory), который позволяет собирать в полученную коллекцию от данного поставщика.

Это гарантирует, что возвращаемая карта является HashMap и что списки в каждом значении ArrayList. Обратите внимание: если вы хотите вернуть конкретные реализации карты и коллекции, то вы, скорее всего, хотите, чтобы метод возвращал эти конкретные типы, поэтому вы можете использовать их свойства.

Если вы хотите указать поставщика коллекции и сохранить карту groupingBy по умолчанию, вы можете просто опустить поставщика в приведенном выше коде и использовать two перегрузка аргументов:

public Map<Integer, List<String>> getMap(List<String> strings) {
    return strings.stream().collect(
      Collectors.groupingBy(String::length, Collectors.toCollection(ArrayList::new))
    );
}

В качестве побочной заметки для этого может быть общий метод:

public <K, V, C extends Collection<V>, M extends Map<K, C>> M getMap(List<V> list,
        Function<? super V, ? extends K> classifier, Supplier<M> mapSupplier, Supplier<C> collectionSupplier) {
    return list.stream().collect(
        Collectors.groupingBy(classifier, mapSupplier, Collectors.toCollection(collectionSupplier))
    );
}

Преимущество этого объявления состоит в том, что теперь вы можете использовать его для получения определенного HashMap of ArrayList в качестве результата или LinkedHashMap of LinkedLists s, если вызывающий абонент пожелает его:

HashMap<Integer, ArrayList<String>> m = getMap(Arrays.asList("foo", "bar", "toto"),
        String::length, HashMap::new, ArrayList::new);
LinkedHashMap<Integer, LinkedList<String>> m2 = getMap(Arrays.asList("foo", "bar", "toto"),
        String::length, LinkedHashMap::new, LinkedList::new);

но в этот момент может быть проще непосредственно использовать groupingBy в коде...

Ответ 2

У меня была аналогичная ситуация. Я решил это как:

Map<String, List<Object>> map = stringList.stream().collect(Collectors.toMap(str -> str, str -> populateList(str)));

И populateList():

private List<Object> populateList(final String str) {
    ...
    ....
    List<Object> list = // dao.get(str);
    return list;
}

Ответ 3

Вы можете использовать это решение, если планируете создать карту, аналогичную Map<property_1, List<property_2>>:

Map<String, List<String>> ds= requestList.stream().collect(
    Collectors.groupingBy(TagRequest::getProperty_1, HashMap::new, 
    Collectors.mapping(TagRequest::getProperty_2, Collectors.toList()))
);

Если вы планируете создать карту, аналогичную Map<property_1, Set<property_2>>, вы можете использовать:

Map<String, List<String>> ds= requestList.stream().collect(
    Collectors.groupingBy(TagRequest::getProperty_1, HashMap::new, 
    Collectors.mapping(TagRequest::getProperty_2, Collectors.toSet()))
);