如何使用Java 8流和自定义List和Map供应商将List<V>转换为Map<K,List<V>>?

45

List<V> 转换为 Map<K,List<V>> 很容易。例如:

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

但我想使用我自己的ListMap供应商来完成它。

我的想法是这样的:

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
有一个重载的 groupingBy,它采用了一个映射(map)供应器(supplier)。 - Tunaki
流中有一个三个参数的收集方法,可能会引起您的兴趣。 - Nick Vanderhoven
3个回答

63

你可以拥有以下内容:

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)收集器可以用于指定想要的Map类型,通过传递一个想要的Map供应商给mapFactory。然后,downstream收集器,用于收集分组到相同键的元素,是toCollection(collectionFactory),它使得可以对从给定供应商获取的集合进行收集。

这样可以确保返回的Map是一个HashMap,而每个值中的列表为ArrayList。请注意,如果您只想返回特定的Map和Collection实现,则很可能也希望该方法返回这些特定类型,以便可以使用它们的属性。

如果您只想指定一个集合供应商,并保留默认映射关系groupingBy,则可以在上面的代码中省略供应商并使用两个参数的重载

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))
    );
}
这种声明的优点在于,现在您可以使用它来获得特定的HashMapArrayListLinkedHashMapLinkedList作为结果,如果调用者需要的话。
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可能更简单...


13

如果您计划创建类似于 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()))
);

1
太棒了!正是我在寻找的。 - PUG
很棒的解决方案!有没有办法解决键已经存在于地图上的情况? - akvyalkov
太好了!某种忍者技巧。今天我学到了新东西。 - Valeriy K.

0

我有类似的情况。我是这样解决的:

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接