4

我遇到了一个示例,该示例表明在方法签名和方法上的擦除方式不同,但我不知道为什么/如何。JLS §8.4.8.3规定:

如果类型声明 T 具有成员方法 m1 并且存在在 T 中声明的方法 m2 或 T 的超类型,则编译时错误,并且满足以下所有条件:

  1. m1 和 m2 具有相同的名称。
  2. m2 可从 T 访问。
  3. m1 的签名不是 m2 签名的子签名(第 8.4.2 节)。
  4. m1 或某些方法 m1 覆盖(直接或间接)的签名与 m2 或某些方法 m2 覆盖(直接或间接)的签名具有相同的擦除。

给出的编译时错误示例

class C<T> {
    T id (T x) {...}
}
class D extends C<String> {
    Object id(Object x) {...}
}

说明:

这是非法的,因为 D.id(Object) 是 D 的成员,C.id(String) 在 D 的超类型中声明,并且:

  • 这两个方法具有相同的名称,id
  • D 可以访问 C.id(String)
  • D.id(Object) 的签名不是 C.id(String) 的子签名
  • 这两种方法具有相同的擦除

前两点很明显,但我不明白解释的最后两点。如果第三点成立,这两种方法如何具有相同的擦除?从第三点来看,签名的擦除似乎是使用参数化类 C<String> 的方法(即 id(String) 而不是 id(T))完成的。如果是这种情况,那么这两个方法应该有不同的擦除,但是该示例表明方法擦除是在非参数化类上完成的。那么,擦除实际上是如何应用于方法签名和方法的呢?

4

1 回答 1

2

擦除意味着所有出现的泛型类型TString在 C 的情况下)都被替换为Object(+ 所需的类型转换)。由于以这种方式丢失了类型信息,因此示例中存在冲突 - JVM 将无法决定调用哪个方法。

编辑(这是错误的)afaik:子签名将是一种接受兼容类型(例如字符串的超类型)和/或返回协变类型的方法。

我试过了,这很令人困惑,但得出了这样的解释:编译器不会擦除,而是替换通用签名。因此,当D extends C<String>覆盖的签名C<String>.id变为:String id(String x). ClearlyD的方法id具有不同的签名和不同的擦除(因为String不是泛型类型)。因此 的签名D.id不是 的子签名C。(符合规则 3)

另一方面, 的擦除C<T>.id(T x)Object id(Object x)的擦除相同D.id(Object x)。(符合规则 4)

之后,id如果可以保持签名和擦除对齐,则覆盖将是有效的。显然这是可能的(虽然不是很有用):

class Foo<T> {
   public void set(final T thing) {}
}

class Bar<T> extends Foo<T> {
   @Override
   public void set(final Object thing) {}
}

这在 eclipse 中编译而没有警告。

于 2013-05-22T13:07:31.900 回答