概述

  • 在先说Java8引入的新特性:接口中的默认方法之前,我们先来聊一聊Java程序的接口。
  • Java的接口使用Interface关键字修饰,在Java7之前接口定义中的方法,必须被子类实现。一旦类库的设计者需要更新接口,向其中加入新的方法,这种方式就会出现问题。所有的实现类都必须实现新增的接口方法。因为现存的实体类往往不在接口设计者的控制范围之内,这些实体类为了适配新的接口约定也需要进行修改。
  • 由于Java 8的API在现存的接口上引入了非常多的新方法,这种变化带来的问题也愈加严重,一个例子就是前几章中使用过的List接口上的sort方法:如果直接添加一个接口方法,那么第三方工具包全部都要升级,比如Guava、Apache Commons,而所有依赖Guava、Apache Commons包的项目,如果要升级JDK版本,也必须升级Guava、Apache Commons包,工作量甚大。
  • Java 8为了解决这一问题引入了一种新的机制。Java 8中的接口现在支持在声明方法的同时提供实现,这听起来让人惊讶!通过两种方式可以完成这种操作。其一,Java 8允许在接口内声明静态方法。其二,Java 8引入了一个新功能,叫默认方法,通过默认方法你可以指定接口方法的默认实现。换句话说,接口能提供方法的具体实现。
  • 因此,实现接口的类如果不显式地提供该方法的具体实现,就会自动继承默认的实现。这种机制可以使你平滑地进行接口的优化和演进。实际上,到目前为止你已经使用了多个默认方法。两个例子就是你前面已经见过的List接口中的sort,以及Collection接口中的stream。
  • Java8之后的默认方法使用default关键字修饰。
  • 你必须要知道的是,向接口添加方法是诸多问题的罪恶之源;一旦接口发生变化,实现这些接口的类往往也需要更新,提供新添方法的实现才能适配接口的变化。如果你对接口以及它所有相关的实现有完全的控制,这可能不是个大问题。但是这种情况是极少的。这就是引入默认方法的目的:它让类可以自动地继承接口的一个默认实现。

    不断演进的API

  • 现在来模拟一个场景,我们来定义一个接口 ```java public interface Resizable { int getWidth();

    int getHeight();

    void setWidth(int width);

    void setHeight(int height); }

  1. - 用户实现[你可能是将`jar包`支持给用户]
  2. ```java
  3. public class Ellipse implements Resizable {
  4. // 各个方法的实现省略
  5. @Override
  6. public int getWidth() {
  7. return 0;
  8. }
  9. @Override
  10. public int getHeight() {
  11. return 0;
  12. }
  13. @Override
  14. public void setWidth(int width) {
  15. }
  16. @Override
  17. public void setHeight(int height) {
  18. }
  19. }
  • 然后用户要求添加新方法,改进API,这时候,你直接加了,如下 ```java public interface Resizable { int getWidth();

    int getHeight();

    void setWidth(int width);

    void setHeight(int height);

    void printInfo(); }

```

  • 这个时候如果用户需要使用新功能,就要升级jar包,如果升级了,那么所有的实现类都要改。更新已发布API会导致后向兼容性问题

    默认方法

  • 默认方法是Java 8中引入的一个新特性,希望能借此以兼容的方式改进API。现在,接口包含的方法签名在它的实现类中也可以不提供实现。那么,谁来具体实现这些方法呢?实际上,缺失的方法实现会作为接口的一部分由实现类继承(所以命名为默认实现),而无需由实现类提供。

    默认方法的使用模式

  • 可选方法

  • 采用默认方法之后,你可以为某种方法提供一个默认的实现,这样实体类就无需在自己的实现中显式地提供一个空方法。
  • 行为的多继承
  • 有了默认方法,那么就可以变相的实现多继承了,因为接口可以定义默认方法。但是不建议这样操作。

    解决冲突的规则

  • 如果一个类使用相同的函数签名从多个地方(比如另一个类或接口)继承了方法,通过三条规则可以进行判断。

  • 类中的方法优先级最高。类或父类中声明的方法的优先级高于任何声明为默认方法的优先级。
  • 如果无法依据第一条进行判断,那么子接口的优先级更高:函数签名相同时,优先选择拥有最具体实现的默认方法的接口,即如果B继承了A,那么B就比A更加具体。
  • 最后,如果还是无法判断,继承了多个接口的类必须通过显式覆盖和调用期望的方法,显式地选择使用哪一个默认方法的实现。
  • 解决冲突的准则
  • 首先,类或父类中显式声明的方法,其优先级高于所有的默认方法。
  • 如果用第一条无法判断,方法签名又没有区别,那么选择提供最具体实现的默认方法的接口。
  • 最后,如果冲突依旧无法解决,你就只能在你的类中覆盖该默认方法,显式地指定在你的类中使用哪一个接口中的方法
  • 关于冲突的,没有给出案例,比较基础,此处省略。

    小结

  • Java 8中的接口可以通过默认方法和静态方法提供方法的代码实现

  • 默认方法的开头以关键字default修饰,方法体与常规的类方法相同
  • 向发布的接口添加抽象方法不是源码兼容的
  • 默认方法的出现能帮助库的设计者以后向兼容的方式演进API
  • 默认方法可以用于创建可选方法和行为的多继承 [不推荐]
  • 我们有办法解决由于一个类从多个接口中继承了拥有相同函数签名的方法而导致的冲突
  • 类或者父类中声明的方法的优先级高于任何默认方法。如果前一条无法解决冲突,那就选择同函数签名的方法中实现得最具体的那个接口的方法
  • 两个默认方法都同样具体时,你需要在类中覆盖该方法,显式地选择使用哪个接口中提供的默认方法

    参考文章

  • 《Java 8 in Action》

  • 《Java8函数式编程》