什么是菱形继承
一张图说明。在C++中允许多继承,D类继承自B、C,而B、C有同一个父类A。那么这个时候调用say方法是否成功?答案是不能,编译器并不能判断这个say来自哪个父类。
#include<iostream>
using namespace std;
class A{
public:
void say(){
cout<<"Say Hello"<<endl;
}
};
class B:public A{
};
class C:public A{
};
class D:public B,public C{
};
int main(){
D d;
d.say(); //出错,返回request for member 'say' is ambiguous
d.B::say();
d.C::say();
return 0;
}
C++的解决办法有两个:一是指定域,使用::
,二是虚继承。
那么,为什么在Java不支持多继承为什么也会出现“菱形继承”呢,这主要是归功于JDK8中出现一个新关键字default
,总所周知,Java支持多继承,那出现了这个default
关键字让interface
的成员方法
也能有实现,那么如果出现这种“多实现”问题,会不会也出现“菱形继承问题”?
Java代码
场景一
Human是超级接口,里面有一个say方法,类F1、类F2继承Human,但并没有重写这个
default
方法
/**
* 菱形继承问题
*/
public class RhombusExtendTest {
private static interface Human{
default void say(){
System.out.println("Human Hello !");
}
}
private static interface F1 extends Human{
default void f1Func(){
System.out.println("F1 f1Func !");
}
}
private static interface F2 extends Human{
default void f2Func(){
System.out.println("F1 f1Func !");
}
}
private static class Son implements F1,F2{
}
public static void main(String[] args) {
Son son = new Son();
son.say();
son.f1Func();
son.f2Func();
}
}
Human Hello !
F1 f1Func !
F1 f1Func !
Process finished with exit code 0
这种情况下,菱形继承没有复现,那么类F1、类F2、类Son都可以访问Human的Say()方法。
场景二
Human是超级接口,里面有一个say方法,类F1、类F2继承Human,并且重写了这个
default
方法
/**
* 菱形继承问题
*/
public class RhombusExtendTest {
private static interface Human{
default void say(){
System.out.println("Human Hello !");
}
}
private static interface F1 extends Human{
@Override
default void say(){
System.out.println("F1 test !");
}
default void f1Func(){
System.out.println("F1 f1Func !");
}
}
private static interface F2 extends Human{
@Override
default void say(){
System.out.println("F2 test !");
}
default void f2Func(){
System.out.println("F1 f1Func !");
}
}
private static class Son implements F1,F2{
}
public static void main(String[] args) {
Son son = new Son();
son.say();//出错!这里编译器不知道应该用类F1还是类F2的say方法
son.f1Func();
son.f2Func();
}
}
编译报错!
RhombusExtendTest.F1 和 RhombusExtendTest.F2 中继承了test() 的不相关默认值
这里便出现了Java版的“菱形继承”的问题。当然,如果你使用了idea这种编译器,它会推荐你去实现这个方法。
简单结论
目前Java8解决这种冲突一般遵循三个原则
- 类中的方法优先级最高。类或父类中声明的方法的优先级高于任何声明为默认方法的优先级。
- 如果无法依据第一条进行判断,那么子接口的优先级更高:函数签名相同时,优先选择 拥有最具体实现的默认方法的接口,即如果B继承了A,那么B就比A更加具体。
- 最后,如果还是无法判断,继承了多个接口的类必须通过
显式覆盖
和调用期望
的方法。
解决办法
那如果说,在Son对象中,就是想调用F1
或者F2
类的say
方法呢?
其实,Java也提供了类似C++中的“域”的概念。
首先这个say
方法肯定不满足前两点,编译器不知道你会使用F1
还是F2
中的say
方法,那么,这里就必须要显示覆盖
。
公式:
类名.super.默认方法
代码如下
/**
* 菱形继承问题
*/
public class RhombusExtendTest {
private static interface Human{
default void say(){
System.out.println("Human Hello !");
}
}
private static interface F1 extends Human{
@Override
default void say(){
System.out.println("F1 test !");
}
default void f1Func(){
System.out.println("F1 f1Func !");
}
}
private static interface F2 extends Human{
@Override
default void say(){
System.out.println("F2 test !");
}
default void f2Func(){
System.out.println("F2 f1Func !");
}
}
private static class Son implements F1,F2{
//这里还是不满足前面三点,需要覆盖say方法
@Override public void say() {
//调用F1中的say方法
F1.super.say();
}
}
public static void main(String[] args) {
Son son = new Son();
son.say();
son.f1Func();
son.f2Func();
}
}
结果如下:正确调用了F1中的say方法
F1 test !
F1 f1Func !
F1 f1Func !
参考:
《Java8实战》