Java 8默认方法继承

13

假设存在以下类型:

public interface Base {

    default void sayHi(){
        System.out.println("hi from base");
    }
}

public interface Foo extends Base {
    @Override
    default void sayHi(){
        System.out.println("hi from foo");
    }
}

public interface Bar extends Base {
}

public class MyClass implements Foo, Bar {
    public static void main(String[] args) {
        MyClass c = new MyClass();
        c.sayHi();
    }
}

在这种情况下,如果执行 main,将会打印出 "hi from foo"。为什么 Foo 的实现会优先?Bar 不是继承自 BasesayHi() 吗?因此,如果 MyClass 只实现了 Bar,那么会调用 Base 的实现。所以代码依然不能编译也说得通。另外,既然 Bar 应该有 BasesayHi() 实现,为什么我不能在 MyClass 中覆盖它呢:
@Override
public void sayHi() {
    Bar.super.sayHi();
}

尝试执行操作时,出现以下错误:

在默认的超类调用方法中出现了错误类型限定符“Bar”,表示方法 sayHi() 在 Foo 中被覆盖


@Casey 你用的是哪个集成开发环境?Eclipse 给出了另一个错误。 - Jorn Vernee
@JornVernee 这是来自 IntelliJ 的。Eclipse 输出的是什么? - blacktide
@Casey 从类型Base到更具体的类型Foo,无法绕过重写的sayHi()方法,因此对超类方法sayHi()的非法引用 - Jorn Vernee
Eclipse的提示信息肯定更清晰。刚刚尝试从命令行进行编译,在Java 1.8.0_45中出现了bad type qualifier Bar in default super call Bar.super.sayHi(); method sayHi() is overridden in Foo的错误信息。 - blacktide
@Casey 我的版本是1.8.0_91,也许问题就在这里。 - Jorn Vernee
3个回答

16

这种行为在JLS 9.4.1中使用了几乎与您的示例完全相同的方式进行说明,只是一些名称发生了变化:

interface Top {
    default String name() { return "unnamed"; }
}
interface Left extends Top {
    default String name() { return getClass().getName(); }
}
interface Right extends Top {}

interface Bottom extends Left, Right {}

Right从Top继承name(),但是Bottom从Left而不是从Right继承name()。这是因为Left中的name()会覆盖Top中的name()声明。

据我所见,JLS似乎并没有给出特别具体的理由;这只是Java设计者决定继承如何工作的方式。


有道理。这有点像设计缺陷。覆盖默认方法会永久隐藏原始实现,而在复杂的层次结构中可能是至关重要的。让具体类决定使用哪个实现似乎是一个更好的想法。 - aiguy
4
@ aiguy,这就是重写的目的。对于普通的类继承来说,覆盖始终都是隐藏超类型实现的一种方式。 - Louis Wasserman
在普通的类继承中,你永远不能有两个父类实现。我的观点是,如果在一个复杂的层次结构中添加了原始默认实现的覆盖,那么如果一些具体类依赖于原始实现,它可能会破坏整个层次结构。 - aiguy
3
@aiguy如果具体类依赖于原始实现,那么它们的做法是错误的。 - Louis Wasserman
@aiguy 嗯,这个回答了你实际提出的问题。另一个回答了你没有实际提出的隐含问题,所以我想这个应该获胜。但是,Casey比Louis更需要声望(抱歉,Louis),所以无论如何请接受另一个回答。;-) - Andreas

13
这是有意设计的。参考JLS 15.12.3:
如果形式为TypeName.super.[TypeArguments] Identifier,则: - 如果TypeName表示一个接口,让T成为立即包围方法调用的类型声明。如果存在一个方法与编译时声明不同且重写(§9.4.1)自T的直接超类或直接超接口的编译时声明,则会发生编译时错误。
在超接口覆盖祖先接口中定义的方法的情况下,此规则防止子接口通过简单地将祖先添加到其直接超接口列表来“跳过”覆盖。访问祖先的功能的适当方式是通过直接超接口,并且仅当该接口选择公开所需行为时才这样做。(或者,开发人员可以自由定义自己的附加超接口,其中包含使用超级方法调用的所需行为。)

0
为什么Foo的实现会优先生效?由于Bar应该从Base继承sayHi()的实现,我为什么不能覆盖它呢?
请查看Oracle的默认方法规则...
“已被其他候选者重写的方法将被忽略。当超类型共享一个共同祖先时,可能会出现这种情况。”
更多信息请参见此处

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接