Java 8 stream.collect( ... groupingBy ( ... mapping( ... сокращение))), сокращающий использование BinaryOperator
Я играл с решением, используя groupingBy
, mapping
а также reducing
на следующий вопрос: Элегантно создайте карту с полями объекта в качестве ключа / значения из потока объекта в Java 8. Подводя итог, цель состояла в том, чтобы получить карту с возрастом в качестве ключа и хобби человека как Set
,
Одно из предложенных мной решений (не очень хорошее, но не главное) имело странное поведение.
Со следующим списком в качестве входных данных:
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"))
);
и следующее решение:
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);
Я получил:
map = {23=[a, b, c, d, e], 24=[a, b, c, d, e]}
явно не то, что я ожидал. Я скорее ожидал этого:
map = {23=[a, d, e], 24=[b, c]}
Теперь, если я просто заменю порядок (strings, strings2)
бинарного оператора (редукционного коллектора) в (strings2, strings)
Я получаю ожидаемый результат. Так что мне здесь не хватало? Я неправильно истолковал reducing
-коллектор? Или какой документ документации я пропустил, что делает очевидным, что мое использование не работает, как ожидалось?
Версия Java - 1.8.0_121, если это имеет значение.
1 ответ
Сокращение никогда не должно изменять входящие объекты. В вашем случае вы модифицируете входящий HashSet
это должно быть значение идентичности и вернуть его, поэтому все группы будут иметь одинаковое HashSet
экземпляр как результат, содержащий все значения.
То, что вам нужно, это изменяемое сокращение, которое может быть реализовано через Collector.of(…)
как это уже было реализовано с помощью встроенных сборщиков Collectors.toList()
, Collectors.toSet()
, так далее.
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;
})));
Причина, по которой нам вообще нужен собственный сборщик, заключается в том, что в Java 8 нет flatMapping
сборщик, который Java 9 собирается представить. При этом решение будет выглядеть так:
Map<Integer, Set<String>> map = personList.stream()
.collect(Collectors.groupingBy(o -> o.age,
Collectors.flatMapping(p -> p.hobbies.stream(), Collectors.toSet())));