我不知道为什么这是有效的替代:
public abstract class A {
public abstract <X> Supplier<X> getSupplier();
public static class B extends A {
@Override
public Supplier<String> getSupplier() {
return String::new;
}
}
}
而这不是:public abstract class A {
public abstract <X> Supplier<X> getSuppliers(Collection<String> strings);
public static class B extends A {
@Override
public Supplier<String> getSuppliers(Collection<String> strings) {
return String::new;
}
}
}
根据JLS §8.4.8.1,B.getSupplier
必须是子签名A.getSupplier
:如果满足以下所有条件,则在类C中声明或由类C继承的实例方法mC将从类C中重写另一个在类A中声明的方法mA:
子签名在JLS §8.4.2中定义:
如果两个方法或构造函数M和N具有相同的名称,相同的类型参数(如果有的话)(第8.4.4节),并且在将N的形式参数类型调整为类型参数之后,则具有相同的签名。 M,形式参数类型相同。
如果满足以下任一条件,则方法m1的签名是方法m2的签名的子签名:
因此,似乎
B.getSupplier
是A.getSupplier
的子签名,但B.getSuppliers
不是A.getSuppliers
的子签名。我不知道怎么回事。
如果
B.getSupplier
是A.getSupplier
的子签名,因为它具有相同的擦除,则B.getSuppliers
也必须具有与A.getSuppliers
相同的擦除。这应该足以覆盖getSuppliers
是合法的-但事实并非如此。如果
B.getSupplier
是A.getSupplier
的子签名,因为它具有相同的签名,那么我想知道“相同类型参数(如果有)”到底是什么意思。如果考虑类型参数,则它们应具有不同的类型参数:
A.getSupplier
具有类型参数X
,B.getSupplier
没有类型参数。如果不考虑类型参数,那么
getSuppliers
有何不同?这更多是关于重写和泛型的学术问题,因此请不要建议重构代码(例如将类型参数
X
移动到类等)。我正在寻找基于JLS的正式答案。
从我的角度来看,
B.getSupplier
应该不能覆盖A.getSupplier
,因为它们没有相同的类型参数。这使得以下代码(产生ClassCastException
)合法:A b = new B();
URL url = b.<URL>getSupplier().get();
最佳答案
根据编译器的输出,两个示例中的方法签名都不同(使用-Xlint:unchecked
选项编译代码以确认代码):
<X>getSupplier() in A (m2)
1st snippet
getSupplier() in B (m1)
<X>getSuppliers(Collection<String> strings) in A (m2)
2nd snippet
getSuppliers(Collection<String> strings) in B (m1)
根据JLS规范,如果满足以下任一条件,则方法m1的签名是方法m2的签名的子签名:第一条陈述是不合时宜的-方法签名是不同的。但是第二个陈述和删除呢?
有效替代
B.getSupplier()
(m1)是A.<X>getSupplier()
(m2)的子签名,因为:擦除后的
<X>getSupplier()
等于getSupplier()
。无效的覆写
B.getSuppliers(...)
(m1)不是A.<X>getSuppliers(...)
(m2)的子签名,因为:m1的签名:
getSuppliers(Collection<String> strings);
删除m2的签名:getSuppliers(Collection strings);
将m1参数从Collection<String>
更改为原始Collection
可消除错误,在这种情况下,m1成为m2的子签名。结论
第一个代码段(有效覆盖):父类和子类中的方法签名最初是不同的。但是,在将擦除应用于父方法之后,签名变得相同。
第二代码段(无效覆盖):方法签名最初是不同的,并且在将擦除应用于父方法之后仍然保持不同。