How to convert List<V> into Map<K, List<V>>, with Java 8 streams and custom List and Map suppliers?

倖福魔咒の 提交于 2020-07-28 06:21:05

问题


It's easy to convert List<V> into Map<K, List<V>>. For example:

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

But I want to do it with my own List and Map suppliers.

I have come up with this:

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));
}

Question: Is there an easier, less verbose, or more efficient way of doing it? For example, something like this (which doesn't work):

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

And what if I only need to define the List supplier, but not the Map supplier?


回答1:


You could have the following:

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

The collector groupingBy(classifier, mapFactory, downstream) can be used to specify which type of map is wanted, by passing it a supplier of the wanted map for the mapFactory. Then, the downstream collector, which is used to collect elements grouped to the same key, is toCollection(collectionFactory), which enables to collect into a collection obtained from the given supplier.

This makes sure that the map returned is a HashMap and that the lists, in each value, are ArrayList. Note that if you want to return specific implementations of map and collection, then you most likely want the method to return those specific types as well, so you can use their properties.

If you only want to specify a collection supplier, and keep groupingBy default map, you can just omit the supplier in the code above and use the two arguments overload:

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

As a side-note, you could have a generic method for that:

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))
    );
}

The advantage with this declaration is that you can now use it to have specific HashMap of ArrayLists as result, or LinkedHashMap of LinkedListss, if the caller wishes it:

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);

but, at that point, it may be simpler to directly use the groupingBy in the code...




回答2:


You could use this solution, if you plan to create a map similar to 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()))
);

If you plan to create a map similar to Map<property_1, Set<property_2>>, you may use:

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



回答3:


I had a similar situation. I solved it like:

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

And populateList() is:

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


来源:https://stackoverflow.com/questions/40772997/how-to-convert-listv-into-mapk-listv-with-java-8-streams-and-custom-list

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!