I created a comparator that never throws NPE. However, when using it inside compareTo, it throws NPE. Why?
public class Person implements Comparable<Person> { public static final Comparator<Person> BIRTHDATE_ASCENDING_NULLS_FIRST = Comparator .nullsFirst(Comparator.comparing(Person::getBirthDate, Comparator.nullsFirst(Comparator.naturalOrder()))); private String name; private LocalDate birthDate; public Person() { super(); } public Person(String name, LocalDate birthDate) { this(); this.name = name; this.birthDate = birthDate; } public String getName() { return name; } public void setName(String name) { this.name = name; } public LocalDate getBirthDate() { return birthDate; } public void setBirthDate(LocalDate birthDate) { this.birthDate = birthDate; } @Override public String toString() { return name + " was born on " + (birthDate == null ? "???" : birthDate); } @Override public int compareTo(Person other) { // BEGIN ADITIONAL TESTS if (other == null) { return 1; } else if (getBirthDate() == null ^ other.getBirthDate() == null) { // nulls first return getBirthDate() == null ? -1 : 1; } else if (getBirthDate() == null) { // both are null return 0; } System.out.println(this.toString() + ", " + other.toString()); // END ADITIONAL TESTS return BIRTHDATE_ASCENDING_NULLS_FIRST.compare(this, other); } @Override public int hashCode() { int result = 1; result = 31 * result + (birthDate == null ? System.identityHashCode(this) : birthDate.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null || getClass() != obj.getClass()) { return false; } return Objects.equals(birthDate, ((Person) obj).getBirthDate()); } public static void main(String[] args) { List<Person> people = new ArrayList<>(); people.add(null); people.add(new Person("John", null)); people.add(new Person("Mary", LocalDate.now().minusYears(20))); people.add(new Person("George", LocalDate.now().minusYears(10))); Collections.sort(people, BIRTHDATE_ASCENDING_NULLS_FIRST); System.out.println(people); Collections.sort(people); System.out.println(people); Collections.sort(people, BIRTHDATE_ASCENDING_NULLS_FIRST.reversed()); System.out.println(people); // This one throws NPE Collections.sort(people); System.out.println(people); }
When expliciting the comparator on Collections.sort
call, sort operation doesn’t use compareTo
implementation, as expected.
When not doing this, sort operation uses the implementation of compareTo
. Since this method calls the exact same comparator, why do I get NPE here? I mean, why the comparator doesn’t handle NPE when being called from compareTo
?
Advertisement
Answer
The other calls to sort
don’t throw NPE because they call Comparator.compare
. The call probably looks something like this:
theComparatorYouPassedToSort.compare(item1, item2)
An actual example is located at TimSort.java line 355:
if (c.compare(a[runHi++], a[lo]) < 0) {
However, the sort
overload that doesn’t take a Comparator
(the one that throws a NPE), calls Person.compareTo
. The call looks something like:
item1.compareTo(item2)
An actual example is located at ComparableTimSort.java line 321:
while (runHi < hi && ((Comparable) a[runHi]).compareTo(a[runHi - 1]) < 0)
Now, no matter what null handling the inner logic of compareTo
does, the above will throw a NPE if item1
is null. Your implementation of compareTo
only prevents an NPE if item2
, the parameter is null. It can handle a null parameter, but it can’t change how Person.compare
is called. If it is called on a null object, an NPE is still thrown.