Collectors.groupingBy (Function, Supplier, Collector) doesn't accept lambda / dosen't see streamed values

二次信任 提交于 2021-02-04 18:51:47

问题


I tried to group values using streams and Collectors. I have list of String which I have to split.

My data:

List<String> stringList = new ArrayList<>();
stringList.add("Key:1,2,3")
stringList.add("Key:5,6,7")

Key is a key in map and 1,2,3 are a values in map

First I have tried using simple toMap

Map<String, List<Integer>> outputKeyMap = stringList.stream()
 .collect(Collectors.toMap(id -> id.split(":")[0], 
          id-> Arrays.stream(id.split(":")[1].split(",")).collect(Collectors.toList());

but it dosen't work because it always create the same key. So I need to use groupingBy function.

Map<String, List<Integer>> outputKeyMap = stringList.stream().collect(groupingBy(id -> id.toString().split(":")[0],
            TreeMap::new,
            Collectors.mapping(id-> Arrays.stream(id.toString().split(":")[1].split(","))
                            .map(Integer::valueOf)
                            .collect(Collectors.toSet()))));

But in this solution compiler dosen't see values passed into lambda functions, and I don't why because Function is as first parameter, and also into Collectors.mapping. In this solution stream doesn't work.

Collectors.groupingBy (Function<? super T, ? extends K> classifier,
                                  Supplier<M> mapFactory,
                                  Collector<? super T, A, D> downstream)

Edit: why groupingBy function doesn't work

I forgot to add Collectors.toSet() in Collectors.mapping as the second parameter. But then I receive Set in Set so it wasn't what am I looking for. There should be used flatMapping but it is in Java9.

    Map<String, Set<Set<String>>> collect = stringList.stream()
                    .collect(groupingBy(id -> id.split(":")[0],
                    TreeMap::new,
                    Collectors.mapping(id-> Arrays.stream(id.toString().split(":")[1].split(","), 
                    Collectors.toSet())

回答1:


You have to use the overload of Collectors.toMap that accepts a merge function:

Map<String, List<Integer>> result = stringList.stream()
        .map(string -> string.split(":"))
        .collect(Collectors.toMap(
                 splitted -> splitted[0],
                 splitted -> Arrays.stream(splitted[1].split(","))
                                   .map(Integer::valueOf)
                                   .collect(Collectors.toCollection(ArrayList::new)),
                 (l1, l2) -> { l1.addAll(l2); return l1; }));

Here (l1, l2) -> { l1.addAll(l2); return l1; } is the merge function. It will be called by the collector whenever there's a key collision. As List.addAll mutates the list, we need to make sure that the first list that is created is mutable, hence the usage of .collect(Collectors.toCollection(ArrayList::new)) in the value mapper function.

I've also optimized the first splitting to a Stream.map operation that is called before collecting, thus avoiding splitting more than once.


The above solution doesn't remove duplicates from the lists. If you need that, you should collect to a Set instead:

Map<String, Set<Integer>> result = stringList.stream()
        .map(string -> string.split(":"))
        .collect(Collectors.toMap(
                 splitted -> splitted[0],
                 splitted -> Arrays.stream(splitted[1].split(","))
                                   .map(Integer::valueOf)
                                   .collect(Collectors.toCollection(LinkedHashSet::new)),
                 (s1, s2) -> { s1.addAll(s2); return s1; }));

Note that LinkedHashSet preserves insertion order.




回答2:


Assuming you don't have duplicate keys in your source list, you can get Map<String, List<Integer>> like:

 Map<String, List<Integer>> result = stringList.stream()
            .collect(toMap(string -> string.split(":")[0],
                    string -> Arrays.stream(string.split(":")[1].split(","))
                            .map(Integer::valueOf)
                            .collect(toList())));

If you have duplicate keys, there is a way with flatMapping from java9:

Map<String, List<Integer>> result = stringList.stream()
           .collect(groupingBy(s -> s.split(":")[0], 
                        flatMapping(s -> Arrays.stream(s.split(":")[1].split(","))
                                           .map(Integer::valueOf), 
                        toList())));

The output will contain all integer values for Key :

{Key=[1, 2, 3, 5, 6, 7]}



来源:https://stackoverflow.com/questions/54520174/collectors-groupingby-function-supplier-collector-doesnt-accept-lambda-do

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