Java 8 stream.collect (... grouping By (... mapping (... abbreviation))) reduced use of BinaryOperator

I played with the solution using groupingBy , mapping and reducing to the following question: Elegantly create a map with object fields as key / value from an object stream in Java 8 . The goal was to get a card with age as a key and a person’s hobby as a Set .

One of the solutions I came up with (not nice, but it's not) was weird behavior.

With the following input:

 List<Person> personList = Arrays.asList( new Person(/* name */ "A", /* age */ 23, /* hobbies */ asList("a")), new Person("BC", 24, asList("b", "c")), new Person("D", 23, asList("d")), new Person("E", 23, asList("e")) ); 

and the following solution:

 Collector<List<String>, ?, Set<String>> listToSetReducer = Collectors.reducing(new HashSet<>(), HashSet::new, (strings, strings2) -> { strings.addAll(strings2); return strings; }); Map<Integer, Set<String>> map = personList.stream() .collect(Collectors.groupingBy(o -> o.age, Collectors.mapping(o -> o.hobbies, listToSetReducer))); System.out.println("map = " + map); 

I got:

 map = {23=[a, b, c, d, e], 24=[a, b, c, d, e]} 

clearly not what i expected. I rather expected this:

 map = {23=[a, d, e], 24=[b, c]} 

Now, if I just replaced the order (strings, strings2) binary operator (reduction collector) with (strings2, strings) , I get the expected result. So what did I miss here? Am I misinterpreting reducing collector? Or what part of the documentation did I skip that makes it obvious that my use is not working properly?

The Java version is 1.8.0_121, if that matters.

+5
source share
1 answer

A shorthand should never modify incoming objects. In your case, you modify the incoming HashSet , which should be an identification value, and return it, so all groups will have the same HashSet instance as the result containing all the values.

What you need is a Mutable Reduction , which can be implemented through Collector.of(…) , as it has already been implemented with pre-assembled collectors Collectors.toList() , Collectors.toSet() , etc.

 Map<Integer, Set<String>> map = personList.stream() .collect(Collectors.groupingBy(o -> o.age, Collector.of(HashSet::new, (s,p) -> s.addAll(p.hobbies), (s1,s2) -> { s1.addAll(s2); return s1; }))); 

The reason we need a custom collector is because Java 8 does not have a flatMapping collector, which Java 9 is about to introduce. In this case, the solution will look like this:

 Map<Integer, Set<String>> map = personList.stream() .collect(Collectors.groupingBy(o -> o.age, Collectors.flatMapping(p -> p.hobbies.stream(), Collectors.toSet()))); 
+9
source

Source: https://habr.com/ru/post/1265834/


All Articles