为什么 List<String> 不能同时是基类泛型方法和派生类非泛型方法的参数?



为什么List<String>不能同时作为基类泛型方法和派生类非泛型方法的参数?

class Base {
<T> void f(List<String> arg) {}
}
class Derived extends Base {
void f(List<String> arg) {}
// above is compile ERROR: method f(List<String>) of type Derived has the same erasure 
//as f(List<String>) of type Base but does not override it
}

我不明白编译器的消息和编译错误的原因。

退货类型没有问题:

class Base {
<T> List<String> f() { return null; }
}
class Derived extends Base {     
List<String> f() { return null; }  // perfectly valid as return-type
}

哇,这是一个有趣的搜索。

这取决于一个方法何时覆盖另一个方法的确切说明。

相关部分在Java语言规范的§8.4.8.1重写(通过实例方法)中(我使用的是Java 14)。

C中声明或继承的实例方法mC,从C重写类A中声明的另一个方法mA,如果以下全部为真:

[…]

  • mC的签名是mA签名的子签名(§8.4.2)

所以让我们看看§8.4.2。方法签名:

如果两个方法或构造函数MN具有相同的名称、相同的类型参数(如果有的话)(§8.4.4),并且在将N的形式参数类型适配为M的类型参数后,具有相同的形式参数类型,则它们具有相同的签名。

方法m1的签名是方法m2的签名的子签名,如果:

m2具有与m1或相同的签名

CCD_ 15的签名与CCD_ 16的签名的擦除(§4.6)相同。

m1m2的子签名或m2m1的子签名时,两个方法签名m1m2是重写等价的。

对于我们的案例,有几件事需要注意:

  1. 返回值不是签名的一部分,因此在决定一个方法是否覆盖另一个方法时,它基本上会被忽略。基于返回类型还有其他约束,但如果两个方法相互重写,但重写确实编译,则这些约束不会产生影响。参见§8.4.8.3覆盖和隐藏要求

  2. 有问题的两个签名不是相同的,因为它们没有相同数量的类型参数。

  3. 它们不是子签名,因为这需要其中一个删除另一个,但两个签名都包含泛型类型(List<String>)。请注意,如果方法签名中没有泛型,即使用List作为参数,或者List<String>仅出现在返回值中,则此情况会发生变化。

=>Derived中的方法不会覆盖Base中的方法。

但它们的消失是一样的。擦除基本上会删除所有类型参数。请参见§4.6类型擦除,了解更为复杂的细节。但这里重要的是,签名的擦除是:void f(List arg)

这违反了§8.4.8.3覆盖和隐藏要求中的一节:

如果类型声明T有一个成员方法m1,并且存在一个在T中声明的方法m2或T的超类型,则这是一个编译时错误,使得以下所有情况都为真:

  • m1和m2具有相同的名称
  • m2可从T
  • m1的签名不是m2签名的子签名(§8.4.2)
  • m1或某个方法m1 overrides (directly or indirectly)has the same erasure as the signature ofm2or some methodm`的签名(直接或间接)覆盖

当然,这就引出了一个问题:为什么要使用subsignature的奇怪定义?

这实际上在§8.4.2中进行了解释。方法签名:

子签名的概念旨在表达两个方法之间的关系,这两个方法的签名不完全相同,但其中一个可以覆盖另一个。具体来说,它允许签名不使用泛型类型的方法覆盖该方法的任何泛型版本。这一点很重要,这样库设计者就可以独立于定义库的子类或子接口的客户端自由地生成方法

问题是您的第一个声明不完整:

<T> void f(List<String> arg) {}de here

在返回类型之前定义的任何泛型通常都应该在方法的参数中使用。因为它没有被使用;对于不完整性,原因编译器首先认为超类与子类中的方法是相似的,因为参数相同,但当它看到超类中涉及一个泛型而子类中没有涉及它时,它无法确定它的确切含义。

返回类型对此没有任何作用,例如,尝试以下操作仍然会显示错误:

class Base {
<T> List<String> f(List<String> arg) { return null; }
}
class Derived extends Base {     
List<String> f(List<String> arg) { return null; }
}

我建议完全遵循泛型语法,以免出现这种奇怪的行为。

最新更新