The problem
In the project I was working on, I had to sort a List of objects of the following class.
public class Person {
private String lastName;
private LocalDate birthDate;
// ... constructor, getters and setters omitted
}
The last name is a mandatory property, birthdate is optional, meaning it can be null. The requirement was to sort a List<Person>, first by last name, then by birthdate. This was my first attempt:
List<Person> persons;
// statements to populate persons
//sort it
persons.sort(Comparator.comparing(Person::getLastName)
.thenComparing(Person::getBirthDate));
This throws a NullPointerException if you have persons with the same last name, and some of them have a null birthdate.
The solution
the Comparator class has two helper methods to either put the null values first either put them last, aptly named nullsFirst and nullLast. Let’s go through it step by step.
persons.sort(Comparator.comparing(Person::getLastName)
.thenComparing(Person::getBirthDate, Comparator.nullsLast(Comparator.naturalOrder())));
We now use the thenComparing method with 2 inputs:
-
a function providing the data to sort on, called the
key- in this case the key value is the birthdate; -
a comparator that is applied to the
keyvalues coming in - we use thenullsLastcomparator but this is a wrapper around the comparator to do the actual comparison. We cannot putComparator.comparing(Person::getBirthDate), because then we are back to square one. -
The solution is to use
Comparator.naturalOrder()which uses the natural order. In case ofLocalDatethe natural order is from old to new, or as defined in the implementation of theComparableinterface.
Sample run
Let’s put it all together and do a test run:
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
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;
}
@Override
public String toString() {
return "Person{" +
"lastName='" + lastName + '\'' +
", birthDate=" + birthDate +
'}';
}
public static void main(String[] args){
List<Person> persons = new ArrayList<>();
persons.add(new Person("Doe", null));
persons.add(new Person("Ray", LocalDate.now()));
persons.add(new Person("Ray", LocalDate.now().minusDays(1)));
persons.add(new Person("Doe", LocalDate.now().plusDays(2)));
persons.add(new Person("Doe", LocalDate.now()));
// sort the list
persons.sort(Comparator.comparing(Person::getLastName)
.thenComparing(Person::getBirthDate, Comparator.nullsLast(Comparator.naturalOrder())));
// print the list
persons.forEach(System.out::println);
}
}
And the sample output:
Person{lastName='Doe', birthDate=2023-05-03}
Person{lastName='Doe', birthDate=2023-05-05}
Person{lastName='Doe', birthDate=null}
Person{lastName='Ray', birthDate=2023-05-02}
Person{lastName='Ray', birthDate=2023-05-03}