lex*_*ore 17 java generics overriding
我想知道为什么这是一个有效的覆盖:
public abstract class A {
public abstract <X> Supplier<X> getSupplier();
public static class B extends A {
@Override
public Supplier<String> getSupplier() {
return String::new;
}
}
}
Run Code Online (Sandbox Code Playgroud)
虽然这不是:
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;
}
}
}
Run Code Online (Sandbox Code Playgroud)
根据JLS§8.4.8.1,B.getSupplier必须是一个副主题A.getSupplier:
在类C中声明或继承的实例方法mC,覆盖C类中声明的另一个方法mA,iff以下所有条件都为真:
- ...
- mC的签名是mA签名的子签名(§8.4.2).
- ...
Subsignatures定义在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();
Run Code Online (Sandbox Code Playgroud)
根据编译器输出,两个示例中的方法签名都不同(使用-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)
Run Code Online (Sandbox Code Playgroud)
根据JLS说明书中,方法m相关的签名1是一个子签名的方法m相关的签名的2如果任:
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);
Run Code Online (Sandbox Code Playgroud)
擦除m 2的签名:
getSuppliers(Collection strings);
Run Code Online (Sandbox Code Playgroud)
将m 1参数从原点更改Collection<String>为原始Collection消除了错误,在这种情况下,m 1成为m 2的子签名.
第一个代码片段(有效覆盖):父类和子类中的方法签名最初是不同的.但是,在将擦除应用于父方法之后,签名变得相同.
第二个代码片段(无效覆盖):方法签名最初不同,并且在将擦除应用于父方法后保持不同.