我遇到了类似于以下内容的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)中指定的,揭示了在检查覆盖等效性之前,超类的形式类型参数在语法上被子类中的实际类型参数替换。

也就是说,与流行的假设相反,覆盖等效性不受擦除的影响。

08-26 11:40