使用祖父窗口的默认方法

我完全迷失了为什么那不起作用:

interface Test { default void doMagic() { System.out.println("Abracadabra"); } } class TestImpl implements Test { } class SpecialTestImpl extends TestImpl { public void doMagic() { Test.super.doMagic(); // Error: No enclosing instance of the type Test is accessible in scope } } 

这是一些奇怪的Eclipse错误消息(它也无法应对Lamdas,所以也许Mars还没有完全准备好Java 8)?

我可以通过让SpecialTestImpl直接实现Test (它产生警告,因为它是不必要的)或覆盖TestImpl的方法(由于相同的原因产生警告)来解决它。

那么为什么我不能调用超级方法呢?

我的猜测是因为如果我能够直接调用Test.super.doMagic() ,那么在TestImpl实现该方法会破坏SpecialTestImpl的API,即使它不应该。 但是,如果我让SpecialTestImpl实现Test并以这种方式调用默认方法,那也是如此。

这不是Eclipse的错误,它是预期的行为。 只需使用super.doMagic(); ,它工作正常。 你不能调用Test.super.doMagic()因为稍后可以在TestImpl超类中重新实现doMagic() 。 在这种情况下, TestImpl实现必须完全遮蔽Test实现,使其无法访问。

虽然很清楚原始代码是否被正确拒绝,但尚未解决,但正如您已经编写的那样,让SpecialTestImpl直接实现Test突然允许您调用default方法。

 interface Test { default void doMagic() { System.out.println("Abracadabra"); } } class TestImpl implements Test { } class SpecialTestImpl extends TestImpl implements Test { public void doMagic() { Test.super.doMagic(); // look, I can invoke that method } } 

有趣的是,当您将doMagic()的具体实现插入到TestImpl类中时,编译器会停止接受此变通方法(至少使用javac )。 所以这个…特征……似乎有点无意义,你可以使用这种调用,只要它与super.doMagic()相比没有效果。 这可能是有意的吗?

我查看了规范,发现了以下内容:

§15.12.1。 编译时步骤1:确定要搜索的类或接口

  • 如果表单是TypeName . super . [TypeArguments] Identifier TypeName . super . [TypeArguments] Identifier TypeName . super . [TypeArguments] Identifier ,然后:

    • 如果TypeName既不是类也不是接口,那么这是一个编译时错误。

    • 否则,TypeName表示要搜索的接口,I。

      设T是紧跟方法调用的类型声明。 如果我不是T的直接超接口,或者如果存在T,J的某个其他直接超类或直接超接口,则J是I的子类型,这是编译时错误。

所以,这里TSpecialTestImplITestT有一个直接的超级超类TestImplTestImplTest的子类型。

因此,无论TestImpl是否具有实际的doMagic()实现,这种解决方法都不应该是可能的,但会引发编译时错误。 所以这是一个似乎涵盖所有现有编译器实现的错误。

这是设计的; 你可以叫你的直接超级方法,但不是你的祖父母。

通过类似于类来考虑这个:

 class A { void m() { } } class B extends A { ... } class C extends B { void m() { super.m(); // OK } } 

想象一下,如果C在A中明确调用实现,绕过B的实现是可以的。这将完全被破坏! B无法强制执行其代表性不变量。 这就是最重要的意思 – 如果B覆盖A中的方法,那么只能从B访问该重写方法。

默认超级调用的限制尝试跟踪此目标(尽管多重inheritance使得这更加棘手。)您可以调用您的直接超级; 你不能通过直接打电话给你的祖父母来结束你的超级巨星。 这同样会破坏覆盖意味着什么。

那么为什么我不能调用超级方法呢?

因为它真的不是super类。 super仅适用于直接子类,它指的是父类。

正如您所说,只需让SpecialTestImpl实现Test并调用默认方法或在超类TestImpl调用实现的方法。