我遇到了类似于以下内容的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();
}
}
(可以将其拆分为文件并运行)。
该代码最终调用了派生的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)
至于在Java语言规范中发现的问题,我也首先错过了它,因为正如人们可能期望的那样,它不是在讨论覆盖或子签名关系的地方指定的,而是在“参数化类型的成员和构造函数” (§4.5.2)中指定的,揭示了在检查覆盖等效性之前,超类的形式类型参数在语法上被子类中的实际类型参数替换。
也就是说,与流行的假设相反,覆盖等效性不受擦除的影响。