我有一个基本的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);
通过记录,我可以看到所做的调用:
- Worker.compareTo()
- Person.compareTo()
所以,这意味着排序方法是混合的,这显然不好。那么用继承来实现 Comparable 以便调用的方法取决于集合的泛型类型的正确方法是什么呢:
- 如果集合是列表,则始终使用 BusinessObject.compareTo()
- 如果集合是列表,则始终使用 Person.compareTo()
- 如果集合是列表,则始终使用 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 提供有意义的信息。
作为旁注,这可能只是由于示例中的代码缩短所致,但您应该始终覆盖 equals
与compareTo
保持一致(即 x.equals(y) 意味着 x.compareTo(y) == 0,反之亦然)。其他任何情况都意味着您的对象的行为方式不直观。 (然后您需要重写 hashCode
来履行它自己的契约(Contract) w.r.t. equals
。重写 equals
但不重写 hashCode
是在 Java 程序中引入真正微妙的错误的绝佳方法。)
关于Java 比较继承,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44495629/