前言
Java 8已经推出相当长一段时间了,其中,接口部分有一些变化。我们来研究下它。
问题
我们知道,对于一个接口,如果我们声明好后,如果再想对其增加新的方法是困难的,因为我们要改变所有其实现类,也就是每个实现类都要对其新方法进行实现。如下图:
这显然是不现实的,如果我们直接把方法写在实现类里,接口中没有此方法,就破坏了我们的多态性。
对于某些已经发布的应用,无论哪种做法都是比较繁重且不被推荐的。
接口默认实现
还好,Java大神们已经意识到了这个问题,于是在Java8中,引入了对接口的默认方法实现。
什么是默认方法实现呢?
简单来说,就是允许接口定义默认方法,在接口中需要有方法体的具体实现,实现类默认继承该方法(如果不重写默认方法的话)。同时为区分默认方法,默认方法在接口中采用default关键字标明。如下图:
这样,如果我们新增一个接口方法(对于已经发布的接口),可以使用默认实现,就不会出现我们上述的问题。
思考
你一定会说,这和抽象类有什么区别呢?
当然还是有区别的,Java8以后,接口和抽象类的几点总结如下:
新的问题
接口引入了默认方法后,就会有新的问题,好在Java已经替我们解决了,我们来看下。
情况一
1 | public interface A { |
结果:输出 Interface B
情况二
1 | public class D implements A { |
结果:输出 Interface B
情况三
如果D是这样呢?
1 | public class D implements A { |
结果:输出Class D
如果D不对doSomething提供实现(D为抽象的类),则C需要为doSomething提供实现。
情况四
如果B接口不在继承A接口。如下:
1 | public interface A { |
那么我们C类必须为doSomething提供实现,当然我们可以具体制定使用哪个接口的doSomething方法,如下:
1 | public class C implements A, B { |
情况五
如果两个函数不一样但差距很小呢?如下:
1 | public interface A { |
在IDEA里我们可以看到,类C是无法编译的,这是不被允许的。
情况六
1 | public interface A { |
输出结果Interface A
因为只有A声明了一个默认方法,这个接口是D的父接口,故输出Interface A。如果B也提供了一个默认方法,签名和A一致,那么编译器会选择B的默认方法,如果B添加一个相同签名的抽象方法,则D需要为其提供实现,如果B,C都有相同签名的默认方法doSomething,则会出现冲突,需要我们为doSomething提供实现或者指定使用B,C中的哪个方法。
结论
解决问题的三条规则:
如果一个类使用相同的函数签名从多个地方(比如另一个类或者接口)继承了方法,通过三条规则可进行判断。
类中的方法优先级最高。类或者父类中声明的方法优先级高于任何声明为默认方法的优先级。
如果无法依据第一条判断,那么子接口的优先级更高:函数签名相同时,优先选择拥有最具体实现的默认方法的接口,及如果B继承了A,那么B就比A更加具体。
最后,如果还是无法判断,继承了多个接口的类必须通过显式覆盖和调用期望的方法,显式地选择使用哪一个默认方法的实现。