我们知道Java语言中一个类只能继承一个父类,但是一个类可以实现多个接口。随着默认方法在Java8中引入,有可能出现一个类继承了多个方法而它们使用的却是同样的函数签名。这种情况下,类会选择使用哪一个函数?在实际情况中,像这样的冲突可能极少发生,但是一旦发生这样的情况,必须要有一套规则来确定按照什么样的约定处理这些冲突。
解决问题的三条规则
如果一个类使用相同的函数签名从多个地方(比如另一个类或接口)继承了方法,通过三条规则可以进行判断。
- 类中的方法优先级最高。类或父类中声明的方法的优先级高于任何声明为默>认方法的优先级。
- 如果无法依据第一条判断,那么子接口的优先级更高:函数签名相同时,优>先选择拥有最具体实现的默认方法的接口,即如果B继承了A,那么B就比A更加具体。
- 最后,如果还是无法判断,继承了多个接口的类必须通过显式覆盖和调用期>望的方法,显式地选择使用哪一个默认方法的实现。
我保证,这就是你需要知道的全部!
选择提供了最具体实现的默认方法的接口
public interface A { default void hello() { System.out.println("Hello from A"); }}public interface B extends A { default void hello() { System.out.println("Hello from B"); }}public interface C implements B, A { public static void main(String...args) { new C().hello(); }}复制代码
编译器会使用声明的哪一个hello方法呢?按照规则(2),应该选择的是提供了最具体实现的默认方法的接口。由于B比A更具体,所以应该选择B的hello方法。所以程序打印输入“Hello from B”。
现在,我们看看如果C像下面那样继承自D,会发生什么情况:public class D implements A{}public class C extends D implements B, A { public static void main(String...args) { new C().hello(); }}复制代码
依据规则(1),类中声明的方法具有更高的优先级。D并未覆盖hello方法,可是它实现了接口A。所以它拥有了接口A的默认方法。规则(2)说如果类或父类没有对应的方法,那么就应该选择提供了最具体实现的接口中的方法。因此,编译器会在接口A和接口B的hello方法之间做选择。由于B更加具体,所以程序会再次打印输入“Hello from B”。
冲突及如何显式地消除歧义
到目前为止,你看到的这些例子都能够应用前两条判断规则解决。让我们更进一步,假设B不再继承A:
public interface A { default void hello() { System.out.println("Hello from A"); }}public interface B { default void hello() { System.out.println("Hello from B"); }}复制代码
这是规则(2)就无法进行判断了,因为从编译器的角度看没有哪一个接口的实现更加具体,两个都差不多。A接口和B接口的hello方法都是有效的选项。所以Java编译器这是就会抛出一个编译错误,因为它无法判断哪一个方法更合适:“Error:class C inherits unrelated default for hello() from types B and A.”
解决这种两个可能的有效方法之间的冲突,没有太多方案;你只能显式地决定你希望在C中使用哪一个方法。为了达到这个目的,你可以覆盖类C中的hello方法,在它的方法体内显式地调用你希望调用的方法。Java8中引入了一种新的语法X.super.m(...),其中X是你希望调用的m方法所在的父接口。举例来说,如果你希望C使用来自于B的默认方法,它的调用方式看起来就如下所示:public class C implements B, A { void hello() { B.super.hello(); }}复制代码