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.