如何转换List<V>使用 Java 8 流和自定义列表和地图供应商进入 Map<K、List<V>>?

2022-01-22 00:00:00 java java-stream collectors

List<V>很容易转换成Map.例如:

public 映射<整数、列表<字符串>>getMap(列表<字符串>字符串) {返回字符串.stream().collect(Collectors.groupingBy(String::length));}

但我想用我自己的 ListMap suppliers 来做.

我想出了这个:

public 映射<整数、列表<字符串>>getMap(列表<字符串>字符串) {返回字符串.stream().collect(Collectors.toMap(字符串::长度,项目->{列表<字符串>list = new ArrayList<>();列表.添加(项目);返回列表;},(list1, list2) ->{list1.addAll(list2);返回列表1;},HashMap::new));}

问题:有没有更简单、更简洁或更有效的方法?例如,像这样的东西(不起作用):

返回字符串.stream().collect(Collectors.toMap(字符串::长度,数组列表::新的,HashMap::new));

如果我只需要定义 List 供应商,而不需要定义 Map 供应商怎么办?

解决方案

你可以有以下:

public 映射<整数、列表<字符串>>getMap(列表<字符串>字符串) {返回 strings.stream().collect(Collectors.groupingBy(String::length, HashMap::new, Collectors.toCollection(ArrayList::new)));}

收集器 groupingBy(classifier, mapFactory,downstream) 可用于指定地图的类型需要,通过将 mapFactory 的所需地图的供应商传递给它.然后,用于收集分组到同一键的元素的下游收集器是 toCollection(collectionFactory),可以收集到从给定供应商处获得的集合.p>

这确保返回的映射是一个HashMap,并且每个值中的列表都是ArrayList.请注意,如果您想要返回 map 和 collection 的特定实现,那么您很可能希望该方法也返回这些特定类型,以便您可以使用它们的属性.

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

public 映射<整数、列表<字符串>>getMap(列表<字符串>字符串) {返回 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列表,函数

此声明的优点是您现在可以使用它来获得 ArrayList 的特定 HashMap 作为结果,或 LinkedHashMapLinkedListss,如果调用者愿意的话:

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

但是,到那时,在代码中直接使用 groupingBy 可能会更简单...

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?

解决方案

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

相关文章