为什么我不能为两个类实现 Comparable,其中一个类继承另一个类呢?

huangapple go评论75阅读模式
英文:

Why I can't implement Comparable for both the classes where one inherits other?

问题

假设我有两个类,Person和Employee。Person类实现了Comparable接口。但是当我尝试为Employee类也实现Comparable接口时,我会得到一个编译器错误。我的代码如下:

class Person implements Comparable<Person> {
    protected int age;
    public Person(int age) {
        this.age = age;
    }
    @Override
    public int compareTo(Person o) {
        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<Person> and Comparable<Employee>

我理解这个错误的原因是类型擦除。因为这个原因,对于这两个类都会添加一个桥接方法,该方法将以Object o作为参数。但这是不允许的。我的理解在这里是正确的吗?

我有一个简单的问题:为什么不能像函数重写那样处理这个问题呢?

英文:

Suppose I have two classes Person and Employee. Person is implementing Comparable. The moment I try to implement Comparable for Employee as well I get a compiler error. My code is:

class Person implements Comparable&lt;Person&gt;{
    		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 &quot;&quot;+age;
    		}
}
    	
class Employee extends Person implements Comparable&lt;Employee&gt;{
    		public Employee(int age) {
    			super(age);
    		}
    		
    		public String toString() {
    			return &quot;&quot;+age;
    		}
}

The error is:

The interface Comparable cannot be implemented more than once with different arguments: Comparable&lt;Hierarchy.Person&gt; and Comparable&lt;Hierarchy.Employee&gt;

I understand that reason for this is Type Erasures. Because of that a Bridge method would be added for both classes which will take Object o as argument. And that is not allowed. Is my understanding correct here?

My silly question is: Why can't that get handled like function overriding?

答案1

得分: 2

关于覆盖和重载的简短解答

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

然而,对于您的第二个方法int compareTo(Employee),它并不是一个覆盖,因为一个对象,而且顺便说一下,一个人,并不总是一个员工。需要进行显式转换。因此,这两个方法的签名不同,所以第二个方法并不是对第一个方法的覆盖。

如果您删除@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(...));
persons.add(new Employee(...));
persons.add(new Employee(...));
persons.add(new Person(...));
...
Collections.sort(persons);

在排序期间,您将比较一个员工和一个人,这将导致ClassCastException异常。在可以对不同类型的元素进行排序的问题上不讨论,您是否真的期望这种情况?

现在假设编译器在类Employee中不生成覆盖的桥接方法,并且要排序的列表只包含Employee类型的对象。
因为Person中的桥接方法只调用int compareTo(Person),所以您的方法int compareTo(Employee)将永远不会被调用。不会抛出异常,但代码可能不会按您的期望执行。

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

让我们看一个问题可能更明显的另一个示例:

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) { ... }
}

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

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
英文:

Short answer about override and overload

Your understanding is globally correct. You can't implement both Comparable&lt;Person&gt; and Comparable&lt;Employee&gt;. Because of type erasure, this would basically lead to two methods int compareTo(Object) with the same name and signature, what isn't allowed.

However, for your second method int compareTo(Employee), it isn't an override precisely because an Object, and by the way neither a Person, isn't always an Employee. An explicit cast would be needed. Therefore, the two methods don't have the same signature, and so the second isn't an override of the first.

If you remove the @Override annotation, it's fine though. Your method isn't an override, but it's a perfectly valid overload.

As a reminder,

  • Overriding is replacing a method by another in a subclass. The override method must have the same name and signature (except return type covariance).
  • Overloading is ahving several methods with the same name in the same class. The methods must have different signatures.

Longer answer about why it isn't allowed

Assume for a moment that implementing Comparable&lt;Person&gt; and Comparable&lt;Employee&gt; would be allowed.

The compiler generates this bridge method in the class Person:

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

When compiling the class Employee, the compiler is similarly supposed to generate this one:

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

As explained above, int compareTo(Employee) can't be an override of int compareTo(Person). However, the second bridge method in Employee above is obviously an override of the first one in Person.
The problems starts here.

Let's say we have this code:

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

You are going to compare an Employee and a Person during the sort and a ClassCastException will be thrown.
Outside the debattable question of being able to sort elements of different types, would you honnestly expect it?

Now let's assume that the compiler doesn't generate the overridding bridge method in class Employee, and that the list to sort exclusively contain objects of type Employee.
Your method int compareTo(Employee) will never be called since the bridge method in Person only calls int compareTo(Person). NO exception will be thrown, but the code probably doesn't do what you expect.

So, what should the compiler do? Override the bridge method or not?
Maybe one of the two solutions is acceptable in your particular case, but the compiler can't guesw which one, if any.

Let's take another example where the problem may be more obvious:

interface I1&lt;T&gt; {
  public void m (T t);
}

interface I2&lt;U&gt; {
  public void m (U u);
}

class A implements I1&lt;A&gt; {
  @Override public void m (A a) { ... }
}

class B extends A implements I2&lt;B&gt; {
  @Override public void m (B b) { ... }
}

Here, the compiler would have to decide whether to call the method of I1 or I2 inside its bridge method void B::m(Object). If you try to compile this code, you are given a better clue of what the problem is:

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

huangapple
  • 本文由 发表于 2020年9月29日 02:37:36
  • 转载请务必保留本文链接:https://go.coder-hub.com/64107804.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定