The problem
I have a list of Person
objects. I would like to group them per birth month, and only keep a List of lastnames.
I came up with this code from memory
Map<String, List<Person>> personsByMonth = persons.stream()
.collect(Collectors.groupingBy(Person::getBirthMonth));
System.out.println(personsByMonth);
And you get the following output (truncated for readability):
{JUNE=[Person{lastName='Doe 11', birthDate=2022-06-07}], JANUARY=[Person{lastName='Doe 10', birthDate=2023-01-26}]}
But how do I get rid of the redundant information?
The solution
The Collectors
class provides quite a lot of variation of the groupingBy
method. The one I need is the two parameter version. It also has a snippet that clarifies a lot. The final code now is:
Map<String, List<String>> namesByMonth = persons.stream()
.collect(Collectors.groupingBy(Person::getBirthMonth, // group by birthMonth
Collectors.mapping(Person::getLastName, Collectors.toList()))); // map each person to their lastName and collect them in a list
Sample run
Let’s put it all together and do a test run:
package com.emakina.vb.poc.rat;
import java.time.LocalDate;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
public class Person {
private String lastName;
private LocalDate birthDate;
public Person(String lastName, LocalDate birthDate) {
this.lastName = lastName;
this.birthDate = birthDate;
}
public String getLastName() {
return lastName;
}
public LocalDate getBirthDate() {
return birthDate;
}
public String getBirthMonth(){
return getBirthDate().getMonth().name();
}
@Override
public String toString() {
return "Person{" +
"lastName='" + lastName + '\'' +
", birthDate=" + birthDate +
'}';
}
// helper method to generate a random date up till a year ago
public static LocalDate randomDate(){
return LocalDate.now().minusDays(new Random().nextInt(366));
}
public static void main(String[] args){
//generate John Doe's
List<Person> persons = IntStream.range(0,20)
.mapToObj(index->new Person("Doe "+ index, randomDate()))
.collect(Collectors.toList());
Map<String, List<Person>> personsByMonth = persons.stream()
.collect(Collectors.groupingBy(Person::getBirthMonth));
System.out.println(personsByMonth);
Map<String, List<String>> namesByMonth = persons.stream()
.collect(Collectors.groupingBy(Person::getBirthMonth,
Collectors.mapping(Person::getLastName, Collectors.toList())));
System.out.println(namesByMonth);
}
}
Sample output (truncated for readability):
{JUNE=[Person{lastName='Doe 11', birthDate=2022-06-07}], JANUARY=[Person{lastName='Doe 10', birthDate=2023-01-26}]}
{JUNE=[Doe 11], JANUARY=[Doe 10], MAY=[Doe 3, Doe 5, Doe 6, Doe 12], MARCH=[Doe 7, Doe 16], FEBRUARY=[Doe 9], AUGUST=[Doe 4, Doe 15], SEPTEMBER=[Doe 17, Doe 19], JULY=[Doe 1, Doe 18], NOVEMBER=[Doe 2, Doe 14], APRIL=[Doe 0, Doe 8, Doe 13]}