添加参数后,覆盖具有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.getSupplier
但B.getSuppliers
不是 A.getSuppliers的子A.getSuppliers
。
我想知道情况如何。
如果B.getSupplier
是A.getSupplier的子A.getSupplier
因为它具有相同的擦除,那么B.getSuppliers
也必须具有与A.getSuppliers
相同的擦除。 这应该足以覆盖getSuppliers
是合法的 – 但事实并非如此。
如果B.getSupplier
是A.getSupplier的A.getSupplier
签名,因为它具有相同的签名,那么我想知道“相同的类型参数(如果有的话)”究竟意味着什么。
如果考虑类型参数,那么它们应该具有不同的类型参数: A.getSupplier
具有类型参数X
, B.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.
(m 2 )的子A.
,因为:
- m 1的签名与m 2签名的擦除相同
擦除后的
等于getSupplier()
。
无效覆盖
B.getSuppliers(...)
(m 1 )不是A.
(m 2 )的子A.
,因为:
- m 1的签名与m 2的签名的擦除不同
m 1的签名:
getSuppliers(Collection strings);
擦除m 2的签名:
getSuppliers(Collection strings);
将m 1参数从Collection
更改为原始Collection
消除错误,在这种情况下,m 1将成为m 2的子签名。
结论
第一个代码片段(有效覆盖) :父类和子类中的方法签名最初是不同的。 但是,在将擦除应用于父方法之后,签名变得相同。
第二个代码片段(无效覆盖) :方法签名最初不同,并且在将擦除应用于父方法后保持不同。
添加参数的那一刻它不再是一个覆盖并成为一个过载。
generics与它无关。