在Java 8 中,接口可以使用default关键字添加方法的默认实现,主要目的是为了解决依赖升级引起不兼容的问题。
当接口中某个新增的方法子类中没有去实现时,就会走接口中的默认实现。
而Java一直是单继承,Java 8 之前不会出现类似C++中的多继承引发的菱形继承问题。
Java 8 接口的默认实现同时也带来菱形继承问题。
假设GrandFather接口如下:
public interface GrandFather {
default void say(){
System.out.println("hello,I'm grandfather");
}
}
而Father1和Father2分别继承了GrandFather接口,并且重写了其say()方法
public interface Father1 extends GrandFather{
@Override
default void say(){
System.out.println("hello,I'm Father1");
}
}
public interface Father2 extends GrandFather{
@Override
default void say(){
System.out.println("hello,I'm Father2");
}
}
新加一个Son子类,同时实现Father1和Father2接口
public class Son implements Father1,Father2{
}
当我们再main()方法中去创建Son对象,调用say()方法
public static void main(String[] args) {
GrandFather son = new Son();
son.say();
}
输出结果(报错):
/Users/test/workspace/idea/demo/src/main/java/com/demo/Son.java:3:8 java: 类 com.demo.Son从类型 com.demo.Father1 和 com.demo.Father2 中继承了say() 的不相关默认值
根据输出结果可知,编译系统帮我们拦截住了错误,因为我们调用Son对象的say()方法,是无法判断具体走哪一个接口中的say()方法的默认实现的。
那么,这个判断标准是什么
- 类方法优先级最高。类或父类中声明的方法的优先级高于任何声明为默认方法的优先级。
- 函数签名相同时,优先选择拥有最具体实现的默认方法的接口。
- 继承多个接口的类必须通过显示覆盖和调用期望方法,显示指定使用哪一个方法的实现。
标准一:类方法优先级最高。类或父类中声明的方法的优先级高于任何声明为默认方法的优先级。以上内容引用自人民邮电出版社的Java 8实现(Java 8 in Action)
这个理解起来较为简单,如果我们在Son类中重写了say()方法,那么该方法优先级最高!
继承关系不变,Son类改为如下:
public class Son implements Father1,Father2{
@Override
public void say() {
System.out.println("hello,I'm Son");
}
}
执行结果如下:
hello,I'm Son Process finished with exit code 0标准二:函数签名相同时,优先选择拥有最具体实现的默认方法的接口
Son改为原来的实现,移除对Father2实现关系,Son类如下:
public class Son implements Father1{
}
当我们调用Son对象中的say()方法时,只会找到Father1和GrandFather中的say()方法
而Father1中的say()方法又是重写GrandFather中的say()方法
对于Son对象而言,Father1中的say()方法更加具体,因此执行时必定调用的是Father1中的say()方法的默认实现
执行结果如下:
hello,I'm Father1 Process finished with exit code 0标准三:继承多个接口的类必须通过显示覆盖和调用期望方法,显示指定使用哪一个方法的实现
回到初始的状态,Son类实现Father1和Father2,
对于Son对象而言,Father1中的say()方法和Father2中的say()方法优先级是一致的,因此我们必须显示指定期望调用的方法
public class Son implements Father1,Father2{
@Override
public void say() {
Father2.super.say();
}
}
指定方式格式:类名+super.+方法名
这种方式可以理解为标准一的变式
由于我们Son中重写了say()方法,根据标准一,调用的时候必定会调用Son中的方法
而在Son中的say()方法中显示指定调用具体接口的具体默认实现
补充:
如果我们使用的IDEA版本比较新,或者安装了一些代码规范和检查的插件的话,我们不指定具体调用的话会给出具体的提示和解决方法的。
所以对于Java而言,我们无需担心代码会有菱形继承问题,不解决菱形继承问题,是无法通过编译阶段的!



