Группировка по атрибуту Collection с использованием потоков Java
У меня есть объект, который содержит коллекцию строк, скажем, языки, на которых говорит человек.
public class Person {
private String name;
private int age;
private List<String> languagesSpoken;
// ...
}
Теперь, создавая несколько экземпляров, как это...
Person p1 = new Person("Bob", 21, Arrays.asList("English", "French", "German"));
Person p2 = new Person("Alice", 33, Arrays.asList("English", "Chinese", "Spanish"));
Person p3 = new Person("Joe", 43, Arrays.asList("English", "Dutch", "Spanish", "German"));
//put them in list
List<Person> people = Arrays.asList(p1,p2,p3);
... то, что я хочу, это Map<String, List<Person>>
, для каждого языка, перечисляющая людей, говорящих на этом языке:
["English" -> [p1, p2, p3],
"German" -> [p1, p3],
etc. ]
Конечно, это может быть легко запрограммировано в обязательном порядке, но как сделать это функционально с помощью Java Streams? Я пробовал что-то вроде people.stream.collect(groupingBy(Person::getLanguagesSpoken))
но это, конечно, дает мне Map<List<String>, List<Person>>
. Все примеры, которые я смог найти, используют groupingBy
для Primitives или Strings.
Ответы
Ответ 1
Вы можете разбить экземпляры Person
на пары языка и Person
(используя flatMap
), а затем сгруппировать их по мере необходимости:
Map<String, List<Person>> langPersons =
people.stream()
.flatMap(p -> p.getLanguagesSpoken()
.stream()
.map(l -> new SimpleEntry<>(l,p)))
.collect(Collectors.groupingBy(Map.Entry::getKey,
Collectors.mapping(Map.Entry::getValue,
Collectors.toList())));
Ответ 2
Это можно обойтись и без потоков, все еще используя новые функции java-8.
people.forEach(x -> {
x.getLanguagesSpoken().forEach(lang -> {
langPersons.computeIfAbsent(lang, ignoreMe -> new ArrayList<>()).add(x);
});
});