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())));
Другие вопросы по тегам