Java中一个类只能继承一个父类,但是一个类可以实现多个接口。随着默认方法在Java 8中引入,有可能出现一个类继承了多个方法而它们使用的却是同样的函数签名。此时类会选择使用哪一个函数?在实际情况中,像这样的冲突可能极少发生,一旦发生,必须要有一套规则来确定按照什么样的约定处理这些冲突。
冲突示例:
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 class C implements B, A {
public static void main(String... args) { new C().hello(); }
}
1、解决问题的三条规则
如果一个类使用相同的函数签名从多个地方(如另一个类或接口)继承了方法,通过三条规则可以进行判断:
1)类中的方法优先级最高。类或父类中声明的方法的优先级高于任何声明为默认方法的优先级。
2)如果无法依据第一条进行判断,那么子接口的优先级更高:函数签名相同时,优先选择拥有最具体实现的默认方法的接口,即如果B继承了A,那么B就比A更加具体。
3)如果还是无法判断,继承了多个接口的类必须通过显式覆盖和调用期望的方法,显式地选择使用哪一个默认方法的实现。
2、选择提供了最具体实现的默认方法的接口
提供最具体的默认方法实现的接口,其优先级更高按照规则(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”。
public class D implements A{
void hello(){ System.out.println("Hello from D"); }
}
public class C extends D implements B, A {
public static void main(String... args) { new C().hello(); }
}
依据规则(1),父类中声明的方法具有更高的优先级,程序会输出“Hello from D”。
3、冲突及如何显式地消除歧义
假设B不再继承A:
public interface A {
void hello() { System.out.println("Hello from A"); }
}
public interface B {
void hello() {System.out.println("Hello from B");}
}
public class C implements B, A { }
此时规则(2)就无法进行判断了,因为从编译器的角度看没有哪一个接口的实现更加具体。A接口和B接口的hello方法都是有效的选项,Java编译器会抛出一个编译错误,因为它无法判断哪一个方法更合适:“Error: class C inherits unrelated defaults for hello() from types B and A.” 。
同时实现具有相同函数声明的两个接口冲突的解决:
解决这种两个可能的有效方法之间的冲突,没有太多方案;你只能显式地决定你希望在C中使用哪一个方法。你可以覆盖类C中的hello方法,在它的方法体内显式地调用你希望调用的方法。Java 8中引入了一种新的语法X.super.m(…),其中X是你希望调用的m方法所在的父接口。
public class C implements B, A {
void hello(){ B.super.hello(); } //显式地选择调用接口B中的方法
}
4、菱形继承问题
public interface A{
default void hello(){ System.out.println("Hello from A"); }
}
public interface B extends A { }
public interface C extends A { }
public class D implements B, C {
public static void main(String... args) { new D().hello(); }
}
菱形继承问题菱形问题:类的继承关系图形状像菱形。实际上只有一个方法声明可以选择,只有A声明了一个默认方法。由于这个接口是D的父接口,代码会打印“Hello from A”。
如果B中也提供了一个默认的hello方法,并且函数签名跟A中的方法也完全一致,会输出什么?根据规则(2),编译器会选择提供了更具体实现 接口中的方法。由于B比A更加具体,所以编译器会选择B中声明的默认方法。如果B和C都使用相同的函数签名声明了hello方法,就会出现冲突,你需要显式地指定使用哪个方法。
如果在C接口中添加一个抽象的hello方法,会出现什么情况?
public interface C extends A { void hello(); }
这个新添加到C接口中的抽象方法hello比由接口A继承而来的hello方法拥有更高的优先级,因为C接口更加具体。因此,类D现在需要为hello显式地添加实现,否则该程序无法通过编译。
总结:如果一个类的默认方法使用相同的函数签名继承自多个接口,需要遵守下面三条准则就能解决所有可能的冲突。
类或父类中显式声明的方法,其优先级高于所有的默认方法。
如果用第一条无法判断,方法签名又没有区别,那么选择提供最具体实现的默认方法的接口。
如果冲突依旧无法解决,就只能在你的类中覆盖该默认方法,显式地指定在类中使用哪一个接口中的方法。
--参考文献《Java8实战》
网友评论