我遇到类似以下的Java代码:
public interface BaseArg {
}
public class DerivedArg implements BaseArg {
}
public abstract class Base <A extends BaseArg> {
A arg;
void doIt() {
printArg(arg);
}
void printArg(A a) {
System.out.println("Base: " + a);
}
}
public class Derived extends Base<DerivedArg> {
void printArg(DerivedArg a) {
System.out.println("Derived: " + a);
}
public static void main(String[] args) {
Derived d = new Derived();
d.arg = new DerivedArg();
d.doIt();
}
}
Run Code Online (Sandbox Code Playgroud)
(随意将其拆分为文件并运行它).
此代码最终调用Derived printArg.我意识到这是唯一符合逻辑的事情.但是,如果我手动在通用Base上执行"擦除",将所有出现的A替换为BaseArg,则覆盖会发生故障.我现在得到Base的printIt版本.
似乎"擦除"不完全 - 不知何故printArg(A a)与printArg(BaseArg a)不同.我在语言规范中找不到任何基础...
我在语言规范中缺少什么?这不是很重要,但它让我烦恼:).
请注意,导出方法是调用.问题是为什么,考虑到它们的擦除签名不是覆盖等价的.
编译类Derived时,编译器实际上发出两个方法:方法printArg(DerivedArg)和合成方法printArg(BaseArg),它根据类型参数无法理解的虚拟机来覆盖超类方法,并委托给printArg (DerivedArg).您可以通过在printArt(DerivedArg)中抛出异常,同时在Base类型的引用上调用异常并检查堆栈跟踪来验证这一点:
Exception in thread "main" java.lang.RuntimeException
at Derived.printArg(Test.java:28)
at Derived.printArg(Test.java:1) << synthetic
at Base.doIt(Test.java:14)
at Test.main(Test.java:39)
Run Code Online (Sandbox Code Playgroud)
至于在Java语言规范中找到这个,我首先也错过了它,因为它不是,正如人们所预料的那样,指定了覆盖或子签名关系的讨论,而是在"参数化类型的成员和构造函数"中(§4.5) .2),它揭示了在检查覆盖等价之前,超类的形式类型参数在语法上被子类中的实际类型参数替换.
也就是说,与普遍的假设相反,覆盖等价不受擦除的影响.