Java 8 grouping using custom collector?
I have the following class.
class Person {
String name;
LocalDate birthday;
Sex gender;
String emailAddress;
public int getAge() {
return birthday.until(IsoChronology.INSTANCE.dateNow()).getYears();
}
public String getName() {
return name;
}
}
I'd like to be able to group by age and then collect the list of the persons names rather than the Person object itself; all in a single nice lamba expression.
To simplify all of this I am linking my current solution that store the result of the grouping by age and then iterates over it to collect the names.
ArrayList<OtherPerson> members = new ArrayList<>();
members.add(new OtherPerson("Fred", IsoChronology.INSTANCE.date(1980, 6, 20), OtherPerson.Sex.MALE, "[email protected]"));
members.add(new OtherPerson("Jane", IsoChronology.INSTANCE.date(1990, 7, 15), OtherPerson.Sex.FEMALE, "[email protected]"));
members.add(new OtherPerson("Mark", IsoChronology.INSTANCE.date(1990, 7, 15), OtherPerson.Sex.MALE, "[email protected]"));
members.add(new OtherPerson("George", IsoChronology.INSTANCE.date(1991, 8, 13), OtherPerson.Sex.MALE, "[email protected]"));
members.add(new OtherPerson("Bob", IsoChronology.INSTANCE.date(2000, 9, 12), OtherPerson.Sex.MALE, "[email protected]"));
Map<Integer, List<Person>> collect = members.stream().collect(groupingBy(Person::getAge));
Map<Integer, List<String>> result = new HashMap<>();
collect.keySet().forEach(key -> {
result.put(key, collect.get(key).stream().map(Person::getName).collect(toList()));
});
Current solution
Not ideal and for the sake of learning I'd like to have a more elegant and performing solution.
When grouping a Stream with Collectors.groupingBy
, you can specify a reduction operation on the values with a custom Collector
. Here, we need to use Collectors.mapping
, which takes a function (what the mapping is) and a collector (how to collect the mapped values). In this case the mapping is Person::getName
, i.e. a method reference that returns the name of the Person, and we collect that into a List
.
Map<Integer, List<String>> collect =
members.stream()
.collect(Collectors.groupingBy(
Person::getAge,
Collectors.mapping(Person::getName, Collectors.toList()))
);
You can use a mapping
Collector
to map the list of Person
to a list of person names :
Map<Integer, List<String>> collect =
members.stream()
.collect(Collectors.groupingBy(Person::getAge,
Collectors.mapping(Person::getName, Collectors.toList())));
You can also use Collectors.toMap and provide mapping for key, value and merge function(if any).
Map<Integer, String> ageNameMap =
members.stream()
.collect(Collectors.toMap(
person -> person.getAge(),
person -> person.getName(), (pName1, pName2) -> pName1+"|"+pName2)
);