基础概念
实现接口的类必须为接口中定义的每个方法提供一个实现,或者从父类中继承它的实现。缺点在于,一旦类库的设计者需要更新接口,向其中加入新的方法,这种方式就会出现问题。
针对这种情况,也为了能让Collection使用Stream, JAVA8中的接口现在支持在声明方法的同时提供实现。
通过2种方式可以完成这种操作:JAVA8允许在接口内声明静态方法,或者,JAVA8引入了一个新功能,叫默认方法。
通过默认方法可以指定接口方法的默认实现,即该接口能提供方法的具体实现,因此,实现接口的类如果不显式地提供该方法的具体实现,那么就会自动继承默认的实现。默认方法只需在方法名前面加default关键字即可。
默认方法会作为接口的一部分由实现类继承,而无需由实现类提供。
扩展
由于JAVA不允许多重继承,但允许接口多重实现,那么,再加上JAVA8的默认方法新特性,那么,我们是不是可以做如下方面的尝试:
接口A提供了名字叫做a的默认方法。
接口B提供了名字叫做b的默认方法。
接口C提供了名字叫做c的默认方法。
..........等等
那么,我们在面对某个新功能开发的时候,只需implements某一个/N个接口,然后再添加新的业务逻辑即可,是不是可以做到很大程度上的代码重用?
真香。
解决冲突的规则
如果多个接口的默认方法,其函数签名都一致,那么当同时implements这几个接口的时候,那么,这么多默认方法到底取哪一个呢?
则按照如下规则进行选择:
①类中的方法优先级最高。类或父类中声明的方法的优先级高于任何声明为默认方法的优先级。
②如果无法依据第一条进行判断,那么子接口的优先级更高:函数签名相同时,优先选择拥有最具体实现的默认方法的接口,即如果B继承了A,那么B就比A更加具体。
③如果第一条和第二条还是无法判断,继承了多个接口的类必须通过显式覆盖和调用期望的方法,显式地选择使用哪一个默认方法的实现。
①例如:C implements B,A。然后你想C使用B的默认方法,则在C里只能显式地决定你希望在C中使用哪一个方法。为了达到这个目的,你可以覆盖类C中的hello方法,在它的方法体内显式地调用你希望调用的方法:X.super.m(…),其中X是你希望调用的m方法所在的父接口。
public class C implements B, A {
void hello(){
B.super.hello();
}
}
②
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();
}
}
由于B和C都是继承了同一个接口A,且都没有重写hello方法,所以,D中的hello是接口A的默认实现。
③如果②中B也提供了一个默认的hello方法,并且函数签名跟A中的方法也完全一致,则根据规则二,编译器会选择提供了更具体实现的接口中的方法。由于B比A更加具体,所以编译器会选择B中声明的默认方法。
④如果B和C都使用相同的函数签名声明了hello方法,就会出现冲突,则根据规则三,需要显式地指定使用哪个方法。
⑤如果你在C接口中添加一个抽象的hello方法(这次添加的不是一个默认方法),则由于新添加到C接口中的抽象方法hello比由接口A继承而来的hello方法拥有更高的优先级,由于C接口更加具体。因此,类D现在需要为hello显式地添加实现,否则该程序无法通过编译,因为这个hello方法是C接口的接口方法,而不是C接口的默认方法。