内容来自《 java8实战 》,本篇文章内容均为非盈利,旨为方便自己查询、总结备份、开源分享。如有侵权请告知,马上删除。
书籍购买地址:java8实战
- 在java8中接口引进了静态方法以及默认方法,通过默认方法,可以为接口方法提供默认实现,也就是说接口能提供方法的具体实现.因此实现接口的类如果不显示的提供该方法的具体实现,就会自动继承默认的实现
- 同时定义接口以及工具辅助类是java常用的一种模式,工具类定义了与接口实例协作的很多静态方法,由于静态方法现在可以存在接口内部,所以代码中的辅助类就没有了存在的必要,可以直接把这些静态方法转移到接口内部
不断演进的API
- 我们来说一下为什么会出现默认方法
- 假如有如下一个接口,你是类库的设计者,那么你写的接口是这样的
public interface Behavior {
void eat(String foodName);
}
- 然后你发布之后大火,你的用户是这样使用的
public class Dog implements Behavior {
@Override
public void eat(String foodName) {
System.out.println("dog " + foodName);
}
}
- 之后你收到了很多意见,说这个行为接口并不丰富,行为不可能只有吃的行为,动物可是都有吃喝拉撒的行为啊!
- 现在你就遇到了问题,如果你将建议的接口添加入Behavior接口,然后发布,这时候你的用户只要更新API的版本,那么他就会遇到一个大麻烦,那么就是实现你在接口中定义的所有方法.这对用户来说是非常不好的.这也是默认方法产生的原因:它可以让你放心的改进接口,无须担心遗留代码的影响,这是因为实现更新接口的类都会自动的集成一个默认的方法实现
不同类型的兼容:二进制,源代码和函数行为
-
变更对java的影响大体可以分为三种类型的兼容:二进制级的兼容,源代码级的兼容,以及函数行为的兼容.
- 二进制级的兼容性表示现有的二进制执行文件能无缝持续链接和运行,比如,为接口添加一个方法就是二进制级的兼容,这种方式下,如果添加的新方法不被调用,接口已经实现的方法可以继续运行,不会出现错误
- 简单的说,源代码级的兼容性表示引入变化之后,现有的程序依然能够成功通过编译
- 函数行为的兼容性表示发生变更后,程序接受同样的输入能得到相同的结果
概述默认方法
- 默认方法就是用default修饰的方法,并像类中声明的其他方法一样包含方法体,并且只要类实现了这个包含默认方法的接口,他就会继承默认方法
- java8中的抽象类和抽象接口区别:首先一个类只能继承一个抽象类,但是一个类可以实现多个接口,其次,一个抽象类可以通过实例变量保存一个通用状态,而接口是不能有实例变量的
默认方法的使用模式
-
可选模式
- 平常我们用类实现一个接口,接口中有很多方法需要我们重新定义,如果有用的方法还好,如果我们并用不到的方法,为了满足接口方法的实现规则,我们就必须在那放一个空方法实现,这也是多余的模板代码,我们可以将这类方法变更为默认方法以实现不必要的空实现
-
行为的多继承
- 行为的多继承值得是:类只能继承一个类,但是可以实现多个接口中的方法,这就是所谓的多继承,现在java8中有了方法的默认实现,那么我们的类就得到了来自不同接口的实现的功能
解决冲突的规则
-
一个类实现了多个接口,而多个接口中含有覆盖实现的方法,那么类会使用那个接口中的方法呢?如果是多个接口中的方法都是相同的方法签名呢?
interface A{ default void say(){ System.out.println("A"); } } interface B extends A { @Override default void say() { System.out.println("B"); } } public class Dog implements B{ public static void main(String[] args) { new Dog().say(); //B } }
-
如果一个类使用相同的函数签名从多个地方继承了方法,通过三条规则可以进行判断
- 类中的方法优先级最高,类或父类中声明的方法的优先级高于任何声明为默认方法的优先级
- 如果无法依据第一条判断,那么就是子类的优先级更高:函数签名相同时,优先选择拥有最具体实现的默认方法的接口,即B继承了A,那么B更具体
- 如果还没办法判断,继承多个接口的类必须通过显示覆盖和调用期望的方法,现实的选择使用哪个默认方法的实现
-
如下
interface A{ default void say(){ System.out.println("A"); } } interface B extends A { @Override default void say() { System.out.println("B"); } } class D implements A{ } public class Dog extends D implements A,B{ public static void main(String[] args) { new Dog().say(); //B } }
- 依照类或父类中声明的方法的优先级高于任何声明为默认方法的优先级,那么就会优先选择D,那么D并没有覆盖掉say方法,但是他默认继承了A的方法,所以它会调用A的方法,但是他会选择更具体的实现,那么就是B,最终也是输出的B
菱形继承问题
interface A{
default void say(){
System.out.println("A");
}
}
interface B extends A {
}
interface C extends A{
}
public class Dog implements B,C{
public static void main(String[] args) {
new Dog().say(); //A
}
}
- 如上,其继承实现关系类似于菱形,Dog实现了B,C但是BC中只是继承了A中的默认方法,所以编译并不会出错,并且Dog也是使用此默认实现,所以输出A
-
如果我们将B加上覆盖实现呢?
interface B extends A { default void say(){ System.out.println("B"); } }
- 现在会输出B,因为覆盖实现的方法更加具体
-
如果两个BC接口都覆盖实现了say方法呢?
//B接口如上变动 interface C extends A{ default void say(){ System.out.println("C"); } }
-
这时候我们就会发现,Dog编译出错了,因为BC都有具体实现,并且都是一个级别的,都是具体实现了A的默认方法,Dog就不知道需要调用谁的方法了,这时候我们只能是在Dog中覆盖这个默认方法的实现了
public class Dog implements B,C{ public static void main(String[] args) { new Dog().say(); //dog } @Override public void say() { System.out.println("dog"); } }
-
如果BC接口只是定义的与A中say方法签名一样的抽象方法呢?
interface A{ default void say(){ System.out.println("A"); } } interface B extends A { void say(); } interface C extends A{ default void say(){ System.out.println("C"); } } public class Dog implements B,C{ public static void main(String[] args) { new Dog().say(); //dog } @Override public void say() { System.out.println("dog"); } }
- 如上只要有一个接口有抽象方法,那么我们就还得按照接口中抽象方法必须实现的规则来
-
所以解决所有可能的冲突只需要三点
- 类或父类中显示声明的方法,其优先级高于任何默认方法,即自己实现的比默认的优先级高
- 如果上面的无法判断,方法签名又没有区别,那么选择提供最具体实现的默认方法的接口,即用接口的最具体的子类的实现
- 如果依旧有冲突,那么就只能在本类中重写覆盖默认方法了