Ask for help about java8 Collectors's groupingBy and mapping

problem description

  • I try to read the following json into a Map < String, List < String > >, map

value whose key is condName,map is condValue

  • now convert List < Condition > to Map < String, List < Condition > >
  • by combining stream with Collectors"s groupingBy and toList methods.
  • then found no way to start when trying to map List < Condition > to List < String > through the mapping method. After several attempts, I got a Map < String, List < List < String > with an extra layer of List
  • if you want to remove this layer of List, it doesn"t feel very elegant if you go through it again
  • do you have any ideas for asking for help? it"s best to get the desired results at once through mapping
  • Thank you very much!

the environmental background of the problems and what methods you have tried

related codes

String json = getContent();
try {
    List<Condition> conditions = deseriliaze(json).getCondition();
    Map<String, List<Condition>> map1 = conditions.stream().collect(Collectors.groupingBy(Condition::getCondName));
    Map<String, List<List<String>>> map2 = conditions.stream().collect(Collectors.groupingBy(Condition::getCondName, Collectors.mapping(Condition::getCondValue, Collectors.toList())));
} catch (Exception e) {
    e.printStackTrace();
}

what result do you expect? What is the error message actually seen?

  • map1:
{name3=[Condition(condName=name3, condValue=[val31, val32, val33])], name2=[Condition(condName=name2, condValue=[val21])], name1=[Condition(condName=name1, condValue=[val11, val12])]}
  • map2:
{name3=[[val31, val32, val33]], name2=[[val21]], name1=[[val11, val12]]}
Aug.25,2021

to answer this question, we can first see why the result of Map < String, List < List < String > appears. This depends on the design semantics of Collectors.groupingBy . It represents a way to classify and group stream data according to certain rules, and to provide a method of how to collect the same group of data, so this is the meaning of Collectors.groupingBy two parameters

.

the first parameter of the subject is Condition::getCondName , and the Condition representing the flow is grouped according to its condName attribute. After grouping, the Condition of the same group is handled. Here the main Collectors.mapping (Condition::getCondValue, Collectors.toList () , mapping represents the mapping transformation. Here it is somewhat similar to grouping. Collectors.mapping the first parameter sets the Condition of the stream. At this point, the stream is List < String > , followed by the second parameter Collectors.toList () , oh, of course, the final result is List < List < String > >

.

the reason why the subject can't get the answer here is that the second parameter of Collectors.mapping is not written correctly. I can think of three ways of
the first: still use Collectors.mapping , similar to the one mentioned by the subject, go over it again,

.
Map<String, List<String>> collect = conditions.stream()
                .collect(Collectors.groupingBy(Condition::getCondName,
                                Collectors.mapping(Condition::getCondValue,
                                        Collectors.collectingAndThen(Collectors.toList(), lists -> lists.stream().flatMap(List::stream).collect(Collectors.toList())))));

here the second parameter of Collectors.mapping uses Collectors.collectingAndThen , which can be seen by the name. The first parameter is, of course, according to Collectors.toList () . After collection, the second parameter traverses List again, flattens it and then forms List

.

emmm, I don't like it very much either, but it just leads to collectingAndThen . Maybe the subject can use it

in the future.

second: also use Collectors.mapping , but this time the second parameter uses Collectors.reducing

Map<String, List<String>> collect2 = conditions.stream()
                .collect(Collectors.groupingBy(Condition::getCondName, 
                        Collectors.mapping(Condition::getCondValue, 
                                Collectors.reducing(new ArrayList<>(), (l1, l2) -> Stream.concat(l1.stream(), l2.stream()).collect(Collectors.toList())))));

here Collectors.reducing is the aggregation of data, which coincides with the current scenario. When the data in the stream is transformed from Condition to List < String > , we want to find a way to merge different List < String > . Therefore, this aggregation method Collectors.reducing is used here. The first parameter is the starting value, and the second parameter represents how to merge the two list .

the second method is better, but here I also think of the third method

third: instead of Collectors.groupingBy , use Collectors.toMap

Map<String, List<String>> collect1 = conditions.stream().collect(
                Collectors.toMap(Condition::getCondName, Condition::getCondValue, (c1, c2) -> Stream.concat(c1.stream(), c2.stream()).collect(Collectors.toList())));

the third way feels a bit simpler than the first two, but it skillfully uses the toMap method. The toMap method is generally used only when the data can have an one-to-one relationship. Most of the time, we only use the two-parameter method, that is, pass in how to get key of map and how to get the two Function of value of map . In business, data can be guaranteed to have an one-to-one relationship. If only two-parameter methods are called, but one-to-many cases do occur during actual use, then calling toMap two-parameter methods will report an error, so there is the toMap three-parameter method. The third parameter represents how to combine the value values of the same key , so this is similar to the idea of the second method. Merge two sets

above is my answer, for reference only

by the way, plus, I generally don't like to write excessively long lambda expressions in the stream, because the flow is supposed to reflect the current process, not all the bad things go down, so the third way, the final collection merge, had better be written as a BinaryOperator , after all, the method can also be a parameter or attribute

.
public static final BinaryOperator<List<String>> listMergeMethod = (l1, l2) -> Stream.concat(l1.stream(), l2.stream()).collect(Collectors.toList());
Map<String, List<String>> collect1 = conditions.stream().collect(
                Collectors.toMap(Condition::getCondName, Condition::getCondValue, listMergeMethod));

it makes me feel better.

Menu