第 13 章 默认方法

本章内容

  • 什么是默认方法
  • 如何以一种兼容的方式改进API
  • 默认方法的使用模式
  • 解析规则

传统上,Java程序的接口是将相关方法按照约定组合到一起的方式。实现接口的类必须为接口中定义的每个方法提供一个实现,或者从父类中继承它的实现。但是,一旦类库的设计者需要更新接口,向其中加入新的方法,这种方式就会出现问题。现实情况是,现存的实体类往往不在接口设计者的控制范围之内,这些实体类为了适配新的接口约定也需要进行修改。由于Java 8 API在现存的接口上引入了非常多的新方法,这种变化带来的问题便愈加严重,一个例子就是前几章中使用过的List接口上的sort方法。想象一下其他备选集合框架的维护人员会多么抓狂吧,像Guava和Apache Commons这样的框架现在都需要修改实现了List接口的所有类,为其添加sort方法的实现。

且慢,其实你不必惊慌。Java 8为了解决这一问题引入了一种新的机制。Java 8中的接口现在支持在声明方法的同时提供实现,这听起来让人惊讶!通过两种方式可以完成这种操作。其一,Java 8允许在接口内声明静态方法。其二,Java 8引入了一个新功能,叫默认方法,通过默认方法你可以指定接口方法的默认实现。换句话说,接口能提供方法的具体实现。因此,实现接口的类如果不显式地提供该方法的具体实现,就会自动继承默认的实现。这种机制可以使你平滑地进行接口的优化和演进。实际上,到目前为止你已经使用了多个默认方法。一个例子是你前面已经见过的List接口中的sort,另一个例子是Collection接口中的stream

第1章中我们看到的List接口中的sort方法是Java 8中全新的方法,它的定义如下:

  1. default void sort(Comparator<? super E> c){
  2. Collections.sort(this, c);
  3. }

请注意返回类型之前的新default修饰符。通过它,能够知道一个方法是否为默认方法。这里sort方法调用了Collections.sort方法进行排序操作。由于有了这个新的方法,现在可以直接通过调用sort,对列表中的元素进行排序。

  1. List<Integer> numbers = Arrays.asList(3, 5, 1, 2, 6);
  2. numbers.sort(Comparator.naturalOrder()); ←---- sortList接口的默认方法

不过除此之外,这段代码中还有些其他的新东西。注意到了吗,我们调用了Comparator.naturalOrder方法。这是Comparator接口的一个全新的静态方法,它返回一个Comparator对象,并按自然序列对其中的元素进行排序(即标准的字母数字方式排序)。

第4章中我们看到的Collection中的stream方法的定义如下:

  1. default Stream<E> stream() {
  2. return StreamSupport.stream(spliterator(), false);
  3. }

我们在之前的几章中大量使用了该方法来处理集合,这里stream方法中调用了StreamSupport.stream方法来返回一个流。你注意到stream方法的主体是如何调用spliterator方法了吗?它也是Collection接口的一个默认方法。

喔噢!这些接口现在看起来像抽象类了吧?是,也不是。它们有一些本质的区别,本章会针对性地进行讨论。但更重要的是,你为什么要在乎默认方法?默认方法的主要目标用户是类库的设计者啊。正如后面所解释的,默认方法的引入就是为了以兼容的方式解决像Java API这样的类库的演进问题的,如图13-1所示。

第 13 章 默认方法 - 图1

图 13-1 向接口添加方法

简而言之,向接口添加方法是诸多问题的罪恶之源。一旦接口发生变化,实现这些接口的类往往也需要更新,提供新添方法的实现才能适配接口的变化。如果你对接口以及它所有相关的实现有完全的控制,那么这可能不是个大问题。但是这种情况是极少的。这就是引入默认方法的目的:它让类可以自动地继承接口的一个默认实现。

因此,如果你是个类库的设计者,那么这一章的内容对你而言会十分重要,因为默认方法为接口的演进提供了一种平滑的方式,你的改动将不会导致已有代码的修改。此外,正如后文会介绍的,默认方法为方法的多继承提供了一种更灵活的机制,可以帮助你更好地规划你的代码结构:类可以从多个接口继承默认方法。因此,即使你并非类库的设计者,也能在其中发现感兴趣的东西。

静态方法及接口

同时定义接口以及工具辅助类(companion class)是Java语言常用的一种模式,工具类定义了与接口实例协作的很多静态方法。比如,Collections就是处理Collection对象的辅助类。由于静态方法可以存在于接口内部,因此你代码中的这些辅助类就没有了存在的必要,你可以把这些静态方法转移到接口内部。为了保持后向的兼容性,这些类依然会存在于Java应用程序的接口之中。

本章结构如下。首先跟你一起剖析一个API演化的用例,探讨由此引发的各种问题。接下来解释什么是默认方法,以及它们在这个用例中如何解决相应的问题。然后展示如何创建自己的默认方法,构造Java语言中的多继承。最后讨论一个类在使用一个签名同时继承多个默认方法时,Java编译器是如何解决可能的二义性(模糊性)问题的。