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.
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.
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.
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.
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())));
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); }); });
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With