以下类在运行时打印“M”。我有点期待“G”。
任何人都可以解释这种行为吗?
interface G {
default void print() {
System.out.println("G");
}
}
class M {
public void print() {
System.out.println("M");
}
}
class GImpl extends M implements G {}
public class Wierd {
public static void main(String[] args) {
G g=new GImpl();
g.print();
}
}
答案 0 :(得分:5)
默认方法用作一种备份方法,这意味着只有在没有该方法的具体实现时才会调用它们。
在查看你的课程时,我们有这样的遭遇顺序:
GImpl
,调用print()
。GImpl
没有print()
,所以上了树。M
确实有一个print()
,所以使用那个。我使用G
看到您的唯一地方是变量类型,完全没问题,因为GImpl
是-a G
。< / p>
如果您想调用G
方法,请考虑以下事项:
interface G {
default void print() {
System.out.println("G");
}
}
class M {
}
class GImpl extends M implements G {}
public class Weird {
public static void main(String[] args) {
G g=new GImpl();
g.print();
}
}
在这里,我们有以下顺序:
GImpl
,调用print()
。GImpl
没有print()
,所以上去。M
没有print()
,正在上升。G
,其默认实现为print()
,因此会被调用。注意:如果没有,您的代码甚至都不会编译。因此,使用默认方法,您无法覆盖已存在的行为。但是,当没有其他行为到位时,您可以添加行为。
答案 1 :(得分:0)
在解析诸如print()
的虚拟方法时,JVM会在超类链中搜索实现以及默认实现的接口的传递闭包。如果在祖先类中找到实现,它将始终优先于接口中的默认实现。
此规则的原因是兼容性。
让我们从略微修改的例子开始:
interface G { }
class M {
void print() { ... }
}
class GImpl extends M implements G { }
...
new GImpl().print();
显然,将调用M的print
方法。
现在考虑通过添加G
的默认实现来演变print
:
interface G {
default void print() { ... }
}
回想一下,默认方法的主要目的是促进界面演变。部分原因是能够向界面添加新方法。另一部分不是破坏现有代码。如果向G
添加默认方法是为了更改现有类(如GImpl
)的行为,则很可能会导致程序中断。另外我认为大多数人会对此感到惊讶。
因此,规则确定超类链中的实现始终优先于接口中的默认实现,因此如果将默认方法添加到接口,程序将不会意外地更改行为。