首页 > 解决方案 > 为什么我不能为继承另一个的两个类实现 Comparable?

问题描述

假设我有两个类 Person 和 Employee。人正在实施 Comparable。当我尝试为 Employee 实现 Comparable 时,我得到了一个编译器错误。我的代码是:

class Person implements Comparable<Person>{
            protected int age;
            public Person(int age) {
                this.age = age;
            }
            @Override
            public int compareTo(Person o) {
                //if( o instanceof Employee) return 0;
                return age - o.age;
            }
            
            public String toString() {
                return ""+age;
            }
}
        
class Employee extends Person implements Comparable<Employee>{
            public Employee(int age) {
                super(age);
            }
            
            public String toString() {
                return ""+age;
            }
}

错误是:

The interface Comparable cannot be implemented more than once with different arguments: Comparable<Hierarchy.Person> and Comparable<Hierarchy.Employee>

我知道原因是Type Erasures。因此,将为两个将Object o作为参数的类添加一个 Bridge 方法。这是不允许的。我的理解在这里正确吗?

我愚蠢的问题是:为什么不能像函数覆盖一样处理?

标签: javagenericsinterfacetype-erasure

解决方案


关于覆盖和过载的简短回答

您的理解在全球范围内是正确的。您不能同时实现Comparable<Person>Comparable<Employee>。由于类型擦除,这基本上会导致两个方法int compareTo(Object)具有相同的名称和签名,这是不允许的。

但是,对于您的第二种方法int compareTo(Employee),它并不是一个重写,因为一个对象,顺便说一下,一个人,并不总是一个雇员。需要一个明确的演员表。因此,这两个方法没有相同的签名,因此第二个不是第一个的覆盖。

如果您删除@Override注释,那很好。您的方法不是override,但它是一个完全有效的重载

提醒一句,

  • 覆盖是用子类中的另一个方法替换一个方法。覆盖方法必须具有相同的名称和签名(返回类型协方差除外)。
  • 重载是在同一个类中使用多个具有相同名称的方法。这些方法必须有不同的签名

关于为什么不允许这样做的更长答案

暂时假设实施Comparable<Person>并将Comparable<Employee>被允许。

编译器在 Person 类中生成这个桥接方法:

public int compareTo (Object o) {
  return compareTo((Person)o);
}

在编译 Employee 类时,编译器同样应该生成这个:

public int compareTo (Object o) {
  return compareTo((Employee)o);
}

如上所述,int compareTo(Employee)不能覆盖int compareTo(Person). 但是,上面 Employee 中的第二个桥接方法显然是对 Person 中第一个的覆盖。问题从这里开始。

假设我们有以下代码:

List persons = new ArrayList();
persons.add(new Person(...));
person.add(new Employee(...));
person.add(new Employee(...));
persons.add(new Person(...));
...
Collections.sort(persons);

您将在排序过程中比较 Employee 和 Person 并且将抛出 ClassCastException。除了能够对不同类型的元素进行排序这个有争议的问题之外,你会诚实地期待它吗?

现在让我们假设编译器没有在 Employee 类中生成覆盖桥接方法,并且要排序的列表只包含 Employee 类型的对象。您的方法int compareTo(Employee)将永远不会被调用,因为 Person 中的桥接方法只调用int compareTo(Person). 不会抛出任何异常,但代码可能不会像您期望的那样。

那么,编译器应该怎么做呢?是否覆盖桥接方法?在您的特定情况下,这两种解决方案中的一种可能是可以接受的,但编译器无法猜测是哪一种(如果有的话)。

让我们再举一个问题可能更明显的例子:

interface I1<T> {
  public void m (T t);
}

interface I2<U> {
  public void m (U u);
}

class A implements I1<A> {
  @Override public void m (A a) { ... }
}

class B extends A implements I2<B> {
  @Override public void m (B b) { ... }
}

在这里,编译器必须决定是在其桥接方法中调用 I1 还是 I2 的方法void B::m(Object)。如果您尝试编译此代码,您会更好地了解问题所在:

error: name clash: class B has two methods with the same erasure, yet neither overrides the other
@Override public void m (B b) {
^
first method:  m(B) in I2
second method: m(A) in I1

推荐阅读