口什么是默认方法
口如何以一种兼容的方式改进API
口默认方法的使用模式
口 解析规则
传统上,Java程序的接口是将相关方法按照约定组合到一起的方式。实现接口的类必须为接口中定义的每个方法提供一个实现,或者从父类中继承它的实现。但是,一旦类库的设计者需要更新接口,向其中加入新的方法,这种方式就会出现问题。现实情况是,现存的实体类往往不在接口设计者的控制范围之内,这些实体类为了适配新的接口约定也需要进行修改。由于Java8的API在现存的接口上引入了非常多的新方法,这种变化带来的问题也愈加严重,一个例子就是前几章中使用过的List接口上的sort方法。想象一下其他备选集合框架的维护人员会多么抓狂吧像Guava和Apache Comons这样的框架现在都需要修改实现了List接口的所有类,为其添加sort方法的实现。
且慢,其实你不必惊慌。Java8为了解决这一问题引人了一种新的机制。Java8中的接口现在支持在声明方法的同时提供实现,这听起来让人惊讶!通过两种方式可以完成这种操作。
其一,Java8允许在接口内声明静态方法。
其二,Java8引人了一个新功能,叫默认方法,通过默认方法你可以指定接口方法的默认实现。换句话说,接口能提供方法的具体实现。因此,实现接口的类如果不显式地提供该方法的具体实现,就会自动继承默认的实现。这种机制可以使你平滑地进行接口的优化和演进。实际上,到目前为止你已经使用了多个默认方法。两个例子就是你前面已经见过的List接日中的sort,以及Collection接日中的stream。第1章中我们看到的List接口中的sort方法是Java8中全新的方法,它的定义如下:
default void sort(Comparator<?super E>c){
Collections.sort(this,c)
}
请注意返回类型之前的新aefau1t修饰符。通过它,我们能够知道一个方法是否为默认方法。这里sort方法调用了collections.sort方法进行排序操作。由于有了这个新的方法,
简而言之,向接口添加方法是诸多问题的罪恶之源;一旦接口发生变化,实现这些接口的类往往也需要更新,提供新添方法的实现才能适配接口的变化。如果你对接口以及它所有相关的实现有完全的控制,这可能不是个大问题。但是这种情况是极少的。这就是引人默认方法的目的:它让类可以自动地继承接口的一个默认实现。
因此,如果你是个类库的设计者,这一章的内容对你而言会十分重要,因为默认方法为接口的演进提供了一种平滑的方式,你的改动将不会导致已有代码的修改。此外,正如我们后文会介绍的,默认方法为方法的多继承提供了一种更灵活的机制,可以帮助你更好地规划你的代码结构:类可以从多个接口继承默认方法。因此,即使你并非类库的设计者,也能在其中发现感兴趣的东西。
静态方法及接口
同时定义接口以及工具辅助类(companion class)是Java语言常用的一种模式,工具类定义了与接口实例协作的很多静态方法。比如,Collections就是处理collection对象的辅助类。由于静态方法可以存在于接口内部,你代码中的这些辅助类就没有了存在的必要,你可以把这些静态方法转移到接口内部。为了保持后向的兼容性,这些类依然会存在于Java应用程序的接口之中。
本章的结构如下。首先,我们会跟你一起剖析一个API演化的用例,探讨由此引发的各种问题。紧接着我们会解释什么是默认方法,以及它们在这个用例中如何解决相应的问题。之后,我们会展示如何创建自己的默认方法,构造Java语言中的多继承。最后,我们会讨论一个类在使用一个签名同时继承多个默认方法时,Java编译器是如何解决可能的二义性(模糊性)问题的。
默认方法9.2
经过前述的介绍,我们已经了解了向已发布的API添加方法,对现存代码实现会造成多大的损害。默认方法是Java8中引人的一个新特性,希望能借此以兼容的方式改进API。现在,接口包含的方法签名在它的实现类中也可以不提供实现。那么,谁来具体实现这些方法呢?实际上,缺失的方法实现会作为接口的一部分由实现类继承(所以命名为默认实现),而无需由实现类提供那么,我们该如何辨识哪些是默认方法呢?其实非常简单。默认方法由defau1t修饰符修饰并像类中声明的其他方法一样包含方法体。比如,你可以像下面这样在集合库中定义一个名为sizea的接口,在其中定义一个抽象方法size,以及一个默认方法
isEmpty:
public interface sized{
int size()idefault boolean isEmpty(){return size()==0
这样任何一个实现了sizea接口的类都会自动继承isEmpty的实现。因此,向提供了默认实现的接口添加方法就不是源码兼容的。
现在,我们回顾一下最初的例子,那个Java画图类库和你的游戏程序。具体来说,为了以兼容的方式改进这个库(即使用该库的用户不需要修改他们实现了Resizable的类),可以使用默,
解决冲突的规则
我们知道Java语言中一个类只能继承一个父类,但是一个类可以实现多个接口。随着默认方法在Java 8中引人,有可能出现一个类继承了多个方法而它们使用的却是同样的函数签名。这种情况下,类会选择使用哪一个函数?在实际情况中,像这样的冲突可能极少发生,但是一旦发生这样的状况,必须要有一套规则来确定按照什么样的约定处理这些冲突。这一节中,我们会介绍Java编译器如何解决这种潜在的冲突。我们试图回答像“接下来的代码中,哪一个hello方法是被c类调用的”这样的问题。注意,接下来的例子主要用于说明容易出问题的场景,并不表示这些场景在实际开发过程中会经常发生。
public interface {default void hello(){System.out.println("Hello from ");
public interface Bextends default void hello(){System.out.println("Hello from B");
public classcimplementsB.public static void main(string...args){new C)helle()i
猜猜打印输出
的是什么?
此外,你可能早就对C++语言中著名的荾形继承问题有所了解,菱形继承问题中一个类同时继承了具有相同函数签名的两个方法。到底该选择哪一个实现呢?Java8也提供了解决这个问题的方案。请接着阅读下面的内容。
9.4.1 解决问题的三条规则
如果一个类使用相同的函数签名从多个地方(比如另一个类或接口)继承了方法,通过三条
规则可以进行判断。
(1)类中的方法优先级最高。类或父类中声明的方法的优先级高于任何声明为默认方法的优先级。
(2)如果无法依据第一条进行判断,那么子接口的优先级更高:函数签名相同时,优先选择
(3)最后,如果还是无法判断,继承了多个接口的类必须通过显式覆盖和调用期望的方法
显式地选择使用哪一个默认方法的实现。
我们保证,这些就是你需要知道的全部!让我们一起看几个例子。
9.4.2 选择提供了最具体实现的默认方法的接口
让我们回顾一下本节开头的例子,这个例子中c类同时实现了B接口和A接口,而这两个接口恰巧又都定义了名为he1lo的默认方法。另外,B继承自。图9-5是这个场景的UML图。