自Java 8起,接口定义的方法不仅仅只可以是抽象方法了,还可以定义带有具体实现的方法,叫做默认方法(其实还多了一个接口静态方法,本文暂且不聊,本文主要讲解接口默认方法)。定义这种方法很简单,就是在接口中编写具体方法,在方法前面添加default关键字,那么实现这个接口的类,自动具备了接口的默认方法的行为,这和继承类同时就具有的父类方法非常像,所以很多人也把这个特性认为是Java的多继承的实现,但其实还是有一些区别的,同时也存在一些类似多继承的问题,我们接下来会说到。

先看看接口默认方法的使用格式,如下:

public interface IDefault {    default void hello() {        System.out.println("接口中的hello");    }}

那么Java 8 为什么要提供接口的默认方法特性呢?

用一句话总结就是,接口的默认方法,能够让接口类库的开发者平滑的进行接口升级改造,而不会对已经使用(实现)接口的用户造成影响。

我们先举一个平时开发中遇到的例子,比如我按照业务开发规范,实现了小明开发的一个订单计算接口 IOrder。

/** * @Auther: www.itzhimei.com * @Description: 订单计算接口 */public interface IOrder {    /**     * 计算订单均价     */    void calOrderPrice();    /**     * 计算订单商品数量     */    void calOrderNum();}

我自己实现接口的代码:

/** * @Auther: www.itzhimei.com * @Description: */public class MyOrderImpl implements IOrder {    @Override    public void calOrderPrice() {        System.out.println("我自己实现的订单价格计算方法");    }    @Override    public void calOrderNum() {        System.out.println("我自己实现的订单商品数量计算");    }}

上线后一直运行很好,但是突然有一天,我的代码在编译阶段就报错了,无法通过编译,原因是因为小明在他的IOrder接口中又增加了一个计算订单总金额的方法

/**  * 计算单位时间内订单总金额*/void calOrderTotalAmount();

这时的我,就不得不在自己的实现类MyOrderImpl中紧急增加一个实现方法,哪怕是我暂时用不到的,也要去写实现方法。

这时如果小明将他要新增的订单金额计算方法声明为默认方法,那么对我当前代码就没有任何影响,并且我的代码还自动具备了他新增的订单金额计算功能。

改造后的IOrder接口:

/** * @Auther: www.itzhimei.com * @Description: 订单计算接口 */public interface IOrder {    /**     * 计算订单均价     */    void calOrderPrice();    /**     * 计算订单商品数量     */    void calOrderNum();    /**     * 计算单位时间内订单总金额     */    default void calOrderTotalAmount() {        System.out.println("小明声明的[订单总金额]默认方法");    }}

理解了上面简单的例子,我们继续再展开讲一下。Java 8新增了接口默认方法,最主要的原因是API的大更新,接口默认方法能够支持接口平滑的升级和演变。比如我们常用的集合类List和ArrayList。

我们先看接口List的类结构图:




java中对时序数据平滑处理函数包 java 平滑处理_System

接口List的类结构图



List接口继承子Collection,Collection接口自JDK1.8加入了两个接口默认方法:

default Stream stream();default Stream parallelStream();

按照上面说的,Java 8 因为API大更新所以加入了接口默认方法,之所以让接口支持了默认方法是因为原来接口机制的限制,1.8之前接口中的方法必须是抽象方法,由子类实现,如果还是按照1.8之前的接口规则,如果想让所有集合类都支持stream()方法,就在Collection接口先定一个抽象stream()方法,那么杯具的事情来了,JDK中所有Collection接口的子类、孙子类等等所有实现类,都需要自己实现一遍stream()方法的具体实现逻辑,哪怕代码逻辑都完全是一样的,每个子类、孙子类都需要实现一遍。

例如ArrayList,它的类继承关系如下:




java中对时序数据平滑处理函数包 java 平滑处理_java中对时序数据平滑处理函数包_02


按照上图的继承关系,我们要么在ArrayList中实现stream(),要么在ArrayList的抽象父类中实现AbstractList中实现stream(),这还只是一个方法,在Java 8中和流相关的新增方法还有很多,对于JDK的开发维护者来说,这样一一实现是一个巨大的工作量。

但是这并不是最悲剧的,最悲剧的是,按照传统接口模式更新的JDK发布后,如果有开发者将自己的项目JDK版本进行更新,悲剧的事情开始了,JAVA 面世这么多年,很多三方包对集合类进行了自定义扩展,使用了这些三方包的开发者们发现,自己的代码出现了茫茫多的报错,提示有未实现的方法,这对整个JAVA圈是不可接受的,任谁也不会这样去更新自己的版本,所以才有了接口的默认方法。

接口的默认方法冲突解决

有了接口的默认方法,就存在一个问题,接口默认方法和实现类中的方法冲突、接口默认方法和其子接口或父接口默认方法冲突、两个没有关系的接口中具有相同默认方法产生的冲突,我们接下来就逐个说明这些冲突的解决办法,或者说是遇到冲突的处理规则。

我们先看一个标准的接口默认方法实现模式,没有任何冲突的例子。

声明一个带有接口,并实现一个接口默认方法

public interface IDefault {    default void hello() {        System.out.println("接口中的hello");    }}

AClass类实现了接口

public class AClass implements IDefault {}

测试

/** * 不存在任何冲突的情况下,实现类直接调用接口默认方法 */public class Client {    public static void main(String[] args) {        AClass aClass = new AClass();        aClass.hello();    }}

输出:

接口中的hello

1、冲突情况:一个类继承的父类和实现的接口都具有同时方法声明

public interface IADefault {    default void hello() {        System.out.println("IADefault接口中的hello");    }}

定义父类

public class AParentClass {    public void hello() {        System.out.println("AParentClass中的hello");    }}

定义子类AClass,继承AParentClass,实现IADefault接口

public class AClass extends AParentClass implements IADefault {}

测试

/** * 继承的父类和实现的接口都具有同时方法声明, * 子类这时自己如果没有实现该方法, * 那么默认调用父类方法 * 总结就是不管是类还是父类中的方法,优先级都高于接口中的默认方法 */public class Client {    public static void main(String[] args) {        AClass aClass = new AClass();        aClass.hello();    }}

输出结果:

AParentClass中的hello

冲突规则总结一:总结就是不管是类还是父类中的方法,优先级都高于接口中的默认方法

2、冲突情况:类实现的接口有继承关系,例如AClass实现了IBDefault,IBDefault接口 继承了IADefault接口,IBDefault和IADefault都有默认方法hello()

public interface IADefault {    default void hello() {        System.out.println("IADefault接口中的hello");    }}public interface IBDefault extends IADefault {    default void hello() {        System.out.println("IBDefault接口中的hello");    }}

AClass实现了IBDefault接口,IBDefault继承了IADefault接口

public class AClass implements IBDefault {}

测试:

/** * 类实现的接口有继承关系,例如AClass实现了IBDefault,IBDefault接口 继承了IADefault接口 * IBDefault和IADefault都有默认方法hello() * 子类这时自己如果没有实现该方法, * 那么优先调用IBDefault接口接口的默认方法hello() * 总结优先选择最具体的默认方法,其实有点类似于优先选择离类最近的默认方法 * AClass实现了IBDefault,IBDefault继承了IADefault,很明显IBDefault离AClass最近,所以选择IBDefault的默认方法 * 类或父类中的同名方法优先级高于这种接口继承的情况 */public class Client {    public static void main(String[] args) {        AClass aClass = new AClass();        aClass.hello();    }}

输出结果:

IBDefault接口中的hello

冲突规则总结二:优先选择最具体的默认方法,其实有点类似于优先选择离类最近的默认方法。AClass实现了IBDefault,IBDefault继承了IADefault,IADefault接口和IBDefault接口都有hello默认方法,很明显IBDefault离AClass最近,所以选择IBDefault的默认方法类或父类中的同名方法优先级高于这种接口继承的情况。

3、冲突情况:实现类同时实现了两个接口,两个接口有相同的默认方法,并且这两个接口没有任何关系

public interface IADefault {    default void hello() {        System.out.println("接口中的hello");    }}public interface IBDefault {    default void hello() {        System.out.println("接口中的hello");    }}

AClass此时无法通过编译

public class AClass implements IADefault, IBDefault {    /*@Override    public void hello() {    }*/    //编译报错    //AClass inherits unrelated defaults for hello()    // from types IADefault and IBDefault}
/** * 实现类同时实现了两个接口,两个接口有相同的默认方法 * 无法编译通过,编译器会让我们自己来实现一个方法解决冲突 *      //编译报错 *     //AClass inherits unrelated defaults for hello() *     // from types IADefault and IBDefault */public class Client {    public static void main(String[] args) {        AClass aClass = new AClass();        aClass.hello();    }}

冲突规则总结三:这种情况代码是无法通过编译的,编译器会让我们自己在实现类中实现一个方法解决冲突。

总结就是如果各个类之间存在继承或实现关系,存在默认方法冲突时,优先选择离当前类最近的方法执行,如果子类有对应方法,执行子类方法,如果子类没有,父类有对应方法,执行父类方法,如果父类也没有,接口有,则执行接口中的默认方法,如果接口存在继承关系,也是优先执行子接口的默认方法。

把上面的几种情况的代码执行一下,来加深理解。