【问题标题】:Why compareTo throws NPE using a comparator who doesn't do that when user separatedly为什么 compareTo 使用比较器抛出 NPE,而比较器在用户分开时不会这样做
【发布时间】:2020-11-05 00:32:24
【问题描述】:

我创建了一个从不抛出 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?

【问题讨论】:

    标签: java comparator comparable


    【解决方案1】:

    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。 compareTo 的实现仅在 item2 参数为空时防止 NPE。它可以处理空参数,但不能改变Person.compare 的调用方式。如果在 null 对象上调用它,仍然会抛出 NPE。

    【讨论】:

    • 虽然 compareTo 被一个一个调用,我的意思是,如果要排序的数组有 4 个元素,那么 compareTo 将被调用 4 次。但是在调试 ComparableTimSort.java 第 321 行时,第一个方法参数具有完整的数组。为什么?
    • @Cássio 是的,它会一一比较它们,但不一定是四次。它可能会进行更多比较,具体取决于您的列表的排序方式。 NPE 是由null.compareTo(anotherPerson) 引起的。为什么你认为第一个参数不应该有完整的数组?相反会是什么?
    • 我认为它应该只有被比较的对,然后,我在 compareTo 上添加了一些附加逻辑,以确保在调用比较器之前两个参数都不为空,它应该按预期工作。
    • compareTo 没有两个参数。它是一种方法,它是对象的一部分,用于比较自己。 john.compareTo(bob)。它直接在john 上执行,使用bob 作为参数。因此,如果变量john 没有引用任何内容(null),则无论如何都无法执行调用,并将导致 NPE。在这方面john.compareTo(bob)someComparator.compare(john, bob) 之间存在巨大差异。连方法都进不去,null.anyMethod()这样的情况已经触发了NPE,甚至还没有开始执行anyMethod
    • 我(终于)明白了。当调用“Collections.sort(people)”时,compareTo 将被调用到 people 上的每个人,这(在我的示例中)将导致 NPE。我没有意识到 NPE 是由对 compareTo 的调用引发的,而不是由对 compareTo 内部的比较器的调用引发的。
    猜你喜欢
    • 2020-03-26
    • 2016-02-14
    • 1970-01-01
    • 2011-02-13
    • 2015-05-01
    • 2017-03-22
    • 2019-12-23
    • 2018-09-13
    相关资源
    最近更新 更多