为什么 compareTo 使用比较器抛出 NPE,当用户分开时不这样做

Why compareTo throws NPE using a comparator who doesn't do that when user separatedly

我创建了一个从不抛出 NPE 的比较器。但是,在 compareTo 中使用它时,它会抛出 NPE。为什么?

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);
    }

当在 Collections.sort 调用中显式比较器时,排序操作没有像预期的那样使用 compareTo 实现。
当不这样做时,排序操作使用 compareTo 的实现。由于此方法调用完全相同的比较器,为什么我会在这里得到 NPE?我的意思是,为什么比较器在从 compareTo?

调用时不处理 NPE

其他对 sort 的调用不会抛出 NPE,因为它们调用 Comparator.compare。该调用可能看起来像这样:

theComparatorYouPassedToSort.compare(item1, item2)

实际示例位于 TimSort.java 第 355 行:

if (c.compare(a[runHi++], a[lo]) < 0) {

但是,不接受 Comparator(抛出 NPE 的那个)的 sort 重载调用 Person.compareTo。调用类似于:

item1.compareTo(item2)

一个实际的例子位于 ComparableTimSort.java 第 321 行:

while (runHi < hi && ((Comparable) a[runHi]).compareTo(a[runHi - 1]) < 0)

现在,无论 compareTo 的内部逻辑如何处理 null,如果 item1 为 null,上面的代码将抛出 NPE。如果 item2,参数为 null,则您的 compareTo 实施仅防止 NPE。它可以处理空参数,但不能改变 Person.compare 的调用方式。如果在空对象上调用它,仍然会抛出 NPE。