在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而言,我们无需担心代码会有菱形继承问题,不解决菱形继承问题,是无法通过编译阶段的!