添加参数后,覆盖具有generics返回类型的方法失败

我想知道为什么这是一个有效的覆盖:

public abstract class A { public abstract  Supplier getSupplier(); public static class B extends A { @Override public Supplier getSupplier() { return String::new; } } } 

虽然这不是:

 public abstract class A { public abstract  Supplier getSuppliers(Collection strings); public static class B extends A { @Override public Supplier getSuppliers(Collection strings) { return String::new; } } } 

根据JLS§8.4.8.1 , B.getSupplier必须是一个子签名A.getSupplier

在类C中声明或inheritance的实例方法mC,覆盖C类中声明的另一个方法mA,iff以下所有条件都为真:

  • mC的签名是mA签名的子签名(第8.4.2节)。

子签名在JLS§8.4.2中定义:

两个方法或构造函数M和N具有相同的签名,如果它们具有相同的名称,相同的类型参数(如果有的话)(第8.4.4节),并且在将forms参数类型N调整为类型参数之后M,相同的forms参数类型。

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

  • m2与m1具有相同的签名,或
  • m1的签名与m2签名的擦除(§4.6)相同。

因此,似乎B.getSupplier是A.getSupplier的子A.getSupplierB.getSuppliers 不是 A.getSuppliers的子A.getSuppliers

我想知道情况如何。

如果B.getSupplier是A.getSupplier的子A.getSupplier因为它具有相同的擦除,那么B.getSuppliers也必须具有与A.getSuppliers相同的擦除。 这应该足以覆盖getSuppliers是合法的 – 但事实并非如此。

如果B.getSupplier是A.getSupplier的A.getSupplier签名,因为它具有相同的签名,那么我想知道“相同的类型参数(如果有的话)”究竟意味着什么。

如果考虑类型参数,那么它们应该具有不同的类型参数: A.getSupplier具有类型参数XB.getSupplier没有。
如果不考虑类型参数那么getSuppliers不同?

这更像是关于覆盖和generics的学术问题所以请不要建议重构代码(比如将类型参数X移动到类等)。

我正在寻找一个基于JLS的正式答案。

从我的观点来看, B.getSupplier不应该覆盖A.getSupplier因为它们没有相同的类型参数。 这使得以下代码(生成ClassCastException )合法:

 A b = new B(); URL url = b.getSupplier().get(); 

根据编译器输出,两个示例中的方法签名都不同(使用-Xlint:unchecked选项编译代码以确认它):

 getSupplier() in A (m2) 1st snippet getSupplier() in B (m1) getSuppliers(Collection strings) in A (m2) 2nd snippet getSuppliers(Collection strings) in B (m1) 

根据JLS规范,方法m 1的签名是方法m 2的签名的子签名,如果:

  • m 2与m 1具有相同的特征, 或者

  • m 1的签名与m 2签名的擦除相同。

第一个声明不在游戏中 – 方法签名不同。 但是第二个声明和删除呢?

有效覆盖

B.getSupplier() (m 1 )是A.getSupplier() (m 2 )的子A.getSupplier() ,因为:

  • m 1的签名与m 2签名的擦除相同

擦除后的getSupplier()等于getSupplier()

无效覆盖

B.getSuppliers(...) (m 1 )不是A.getSuppliers(...) (m 2 )的子A.getSuppliers(...) ,因为:

  • m 1的签名与m 2的签名的擦除不同

m 1的签名:

 getSuppliers(Collection strings); 

擦除m 2的签名:

 getSuppliers(Collection strings); 

将m 1参数从Collection更改为原始Collection消除错误,在这种情况下,m 1将成为m 2的子签名。

结论

第一个代码片段(有效覆盖) :父类和子类中的方法签名最初是不同的。 但是,在将擦除应用于父方法之后,签名变得相同。

第二个代码片段(无效覆盖) :方法签名最初不同,并且在将擦除应用于父方法后保持不同。

添加参数的那一刻它不再是一个覆盖并成为一个过载。

generics与它无关。