Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Group by a Collection attribute using Java Streams

Tags:

I have an object that contains a Collection of strings, let's say the languages that a person speaks.

public class Person {    private String name;    private int age;    private List<String> languagesSpoken;     // ... } 

Now, creating some instances like this...

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); 

... what I want to have is a Map<String, List<Person>>, for every language listing the persons that speak the language:

["English" -> [p1, p2, p3],  "German"  -> [p1, p3],  etc. ] 

Of course this can be programmed easily in an imperative way, but how to do it the functional way with Java Streams? I have tried something like people.stream.collect(groupingBy(Person::getLanguagesSpoken)) but that of course gives me a Map<List<String>, List<Person>>. All the examples I could find, are using groupingBy on Primitives or Strings.

like image 858
user3579358 Avatar asked Oct 25 '18 08:10

user3579358


People also ask

How do you do Groupby in java?

The groupingBy() method of Collectors class in Java are used for grouping objects by some property and storing results in a Map instance. In order to use it, we always need to specify a property by which the grouping would be performed. This method provides similar functionality to SQL's GROUP BY clause.

How does Groupby work in java 8?

In Java 8, you retrieve the stream from the list and use a Collector to group them in one line of code. It's as simple as passing the grouping condition to the collector and it is complete. By simply modifying the grouping condition, you can create multiple groups.

What does stream of () method in java?

Stream of(T t) returns a sequential Stream containing a single element. Syntax : static Stream of(T t) Parameters: This method accepts a mandatory parameter t which is the single element in the Stream. Return Value: Stream of(T t) returns a sequential Stream containing the single specified element.


2 Answers

You can break the Person instances into pairs of language and Person (using flatMap) and then group them as required:

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()))); 
like image 174
Eran Avatar answered Oct 13 '22 22:10

Eran


This is possible to do without streams too, still using java-8 new features.

people.forEach(x -> {         x.getLanguagesSpoken().forEach(lang -> {             langPersons.computeIfAbsent(lang, ignoreMe -> new ArrayList<>()).add(x);         }); }); 
like image 35
Eugene Avatar answered Oct 13 '22 22:10

Eugene