Java Comparable 继承

Gae*_* L. 0 java comparable

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

业务对象 < 人员 < 工人

所以现在我覆盖了 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);
Run Code Online (Sandbox Code Playgroud)

通过记录,我可以看到进行的调用:

  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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

Seb*_*edl 5

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

首先,你有BusinessObjectwhich 实现Comparable<BusinessObject>. 这意味着业务对象有一个自然顺序,由它们的 ID 决定。

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

那没有意义。按 ID 排序对于业务对象来说是自然的,或者不是。但这对他们中的一些人来说是不自然的,但对另一些人来说却不是。

更正式地说,您的覆盖打破了 的传递性要求Comparable。假设您有一个由三个业务对象组成的列表:Worker Alice (ID 3)、Worker Bob (ID 1) 和 Company ACME (ID 2)。传递性说如果x < y && y < z那么x < z也一定是真的。但是,您的比较方法将给出Bob < ACME(compare by IDs) 和ACME < Alice(compare by IDs),但传递性Bob < Alice为 false (compare by names)。您违反了compareTo方法中记录的要求:

实现者还必须确保关系是可传递的:(x.compareTo(y)>0 && y.compareTo(z)>0) 意味着 x.compareTo(z)>0。

底线:Comparable不打算使用开放继承。不要那样做。相反,Comparator在有意义的地方为您的集合使用显式s。

作为旁注,这可能只是由于您的示例中的代码缩短了,但您应该始终重写equals以与compareTo(iexequals(y) 表示 x.compareTo(y) == 0,反之亦然) 保持一致。其他任何事情都意味着您的对象以不直观的方式运行。(然后您需要覆盖hashCode以履行其自己的合约 wrt equals。覆盖equals但不是hashCode将真正微妙的错误引入 Java 程序的绝妙方法。)