Java 比较继承

标签 java comparable

我有一个基本的BusinessObject抽象类,它通过比较它们的long id字段来实现Comparable。现在想象一下,我用 Person 扩展它,然后用 Worker 扩展 Person。所以我们有:

BusinessObject <人< worker

所以现在我在 Person(比较名称)和 Worker(比较作业名称,然后比较人员名称)中的业务对象中重写compareTo(BusinessObject)。

现在我做这样的事情:

List<BusinessObject> collection = new ArrayList<>();
collection.add(new Worker(1L, "Steve", "janitor"));
collection.add(new Worker(2L, "Mark", "plumber"));
collection.add(new Person(3L, "Dave"));

Collections.sort(collection);
System.out.println(collection);

通过记录,我可以看到所做的调用:

  1. Worker.compareTo()
  2. Person.compareTo()

所以,这意味着排序方法是混合的,这显然不好。那么用继承来实现 Comparable 以便调用的方法取决于集合的泛型类型的正确方法是什么呢:

  1. 如果集合是列表,则始终使用 BusinessObject.compareTo()
  2. 如果集合是列表,则始终使用 Person.compareTo()
  3. 如果集合是列表,则始终使用 Worker.compareTo()

这是我的代码:

public abstract class BusinessObject implements HasId, HasText, Comparable<BusinessObject> {
    protected @Nullable Long id;

    public BusinessObject(@Nullable Long id) {
        this.id = id;
    }

    @Override
    public @Nullable Long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    @Override
    public int compareTo(@NonNull BusinessObject o) {
        System.out.println("compareTo BusinessObject");
        return Long.compare(id, o.id);
    }

    @Override
    public String toString() {
        return String.format("BusinessObject#%d", id);
    }
}

public class Person extends BusinessObject {
    protected final String name;

    public Person(@Nullable Long id, String name) {
        super(id);
        this.name = name;
    }

    @NonNull
    @Override
    public String getText(Context context) {
        return null;
    }

    @Override
    public String toString() {
        return String.format("Person#%d (%s)", id, name);
    }

    @Override
    public int compareTo(@NonNull BusinessObject o) {
        if (o instanceof Person) {
            System.out.println("compareTo Person");
            Person po = (Person) o;
            return name.compareTo(po.name);
        }
        else {
            return super.compareTo(o);
        }
    }
}

public class Worker extends Person {
    protected final String job;

    public Worker(@Nullable Long id, String name, String job) {
        super(id, name);
        this.job = job;
    }

    @Override
    public String toString() {
        return String.format("Worker#%d (%s-%s)", id, name, job);
    }

    @Override
    public int compareTo(@NonNull BusinessObject o) {
        if (o instanceof Worker) {
            System.out.println("compareTo Worker");
            Worker wo = (Worker) o;
            return String.format("%s%s", name, job).compareTo(String.format("%s%s", wo.name, wo.job));
        }
        else {
            return super.compareTo(o);
        }
    }
}

最佳答案

你的设计本质上是有缺陷的。

首先,你有BusinessObject它实现了 Comparable<BusinessObject> 。这说明业务对象具有由其 ID 决定的自然顺序。

然后你重写 Person 中的方法来比较名字。您在这里所说的是“好吧,如果被比较的两个业务对象都是人,那么他们的自然顺序是不同的”。

这没有道理。对于业务对象来说,按 ID 排序要么是自然的,要么不是。但这对于其中一些人来说可能是不自然的,但对于其他人来说则不然。

更正式地说,您的覆盖打破了 Comparable 的传递性要求。假设您有一个包含三个业务对象的列表: worker Alice (ID 3)、 worker Bob (ID 1) 和公司 ACME (ID 2)。传递性表示如果 x < y && y < z然后x < z也一定是真的。但是,您的比较方法将给出 Bob < ACME (按 ID 比较)和 ACME < Alice (通过 ID 进行比较),但是及物 Bob < Alice为假(按名称比较)。您违反了 compareTo 中记录的要求方法:

The implementor must also ensure that the relation is transitive: (x.compareTo(y)>0 && y.compareTo(z)>0) implies x.compareTo(z)>0.

底线:Comparable不适合与开放继承一起使用。不要那样做。相反,使用显式 Comparator为您的 Collection 提供有意义的信息。

作为旁注,这可能只是由于示例中的代码缩短所致,但您应该始终覆盖 equalscompareTo保持一致(即 x.equals(y) 意味着 x.compareTo(y) == 0,反之亦然)。其他任何情况都意味着您的对象的行为方式不直观。 (然后您需要重写 hashCode 来履行它自己的契约(Contract) w.r.t. equals 。重写 equals 但不重写 hashCode 是在 Java 程序中引入真正微妙的错误的绝佳方法。)

关于Java 比较继承,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44495629/

相关文章:

java - Comparable/Comparator 使用的排序的内部工作

java - Grails UnitTest 确保 BigDecimal 不是字符串

Java、常量类的接口(interface)或组合

java - 每层集成测试是一个好习惯吗?

java - Netbeans java 使用 Comparable 排序

java - 从 array.sort() 到compareTo()

java - 何时使用 Comparable 和 Comparator

java - 如何使用 Java Media Framework (JMF) 发送 rtcp 再见消息?

java - 当用户按 "enter"键或空格输入时,会弹出错误消息

java - 为什么 Java 不接受我的 Generic LinkedList,却接受它自己的 LinkedList?