我想知道为什么这是一个有效的覆盖:
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类中声明或继承的实例方法mC,覆盖C类中声明的另一个方法mA,iff以下所有条件都为真:
- ...
- mC的签名是mA签名的子签名(§8.4.2)。
- ...
子签名在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();
答案 0 :(得分:5)
根据编译器输出,两个示例中的方法签名都不同(使用-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规范,方法m 1 的签名是方法m 2的签名的子签名 if:
- 具有相同的签名
m 2 与m 1 ,或
m 1 的签名与m 2 的签名擦除相同。
第一个声明不在游戏中 - 方法签名不同。但是第二个声明和删除呢?
B.getSupplier()
(m 1 )是A.<X>getSupplier()
(m 2 )的子签名,因为:
擦除后
- m 1 的签名与m 2 的签名擦除相同
<X>getSupplier()
等于getSupplier()
。
B.getSuppliers(...)
(m 1 )不是A.<X>getSuppliers(...)
(m 2 )的子签名,因为:
- m 1 的签名与m 2
签名的删除不同
m 1 的签名:
getSuppliers(Collection<String> strings);
擦除m 2 的签名:
getSuppliers(Collection strings);
将m 1 参数从Collection<String>
更改为原始Collection
消除了错误,在这种情况下,m 1 成为m <的子签名子> 2 子>
第一个代码段(有效覆盖):父类和子类中的方法签名最初是不同的。但是,在将擦除应用于父方法之后,签名变得相同。
第二个代码段(无效覆盖):方法签名最初不同,并且在将擦除应用于父方法后仍然不同。
答案 1 :(得分:1)
当你添加参数时,它不再是一个覆盖并成为一个过载。
泛型与它无关。