使用祖父窗口的默认方法
我完全迷失了为什么那不起作用:
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的子类型,这是编译时错误。
所以,这里T
是SpecialTestImpl
, I
是Test
, T
有一个直接的超级超类TestImpl
, TestImpl
是Test
的子类型。
因此,无论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
调用实现的方法。