13.2 概述默认方法
经过前述的介绍,我们已经了解了向已发布的API添加方法,对现存代码实现会造成多大的损害。默认方法是Java 8中引入的一个新特性,希望能借此以兼容的方式改进API。现在,接口包含的方法签名在它的实现类中也可以不提供实现。那么,谁来具体实现这些方法呢?实际上,缺失的方法实现会作为接口的一部分由实现类继承(所以命名为默认实现),而无须由实现类提供。
那么,该如何辨识哪些是默认方法呢?其实非常简单。默认方法由default修饰符修饰,并像类中声明的其他方法一样包含方法体。比如,你可以像下面这样在集合库中定义一个名为Sized的接口,在其中定义一个抽象方法size,以及一个默认方法isEmpty:
public interface Sized {int size();default boolean isEmpty() { ←---- 默认方法return size() == 0;}}
这样任何一个实现了Sized接口的类都会自动继承isEmpty的实现。因此,向提供了默认实现的接口添加方法就不是源码兼容的。
现在,回顾一下最初的例子,即那个Java画图类库和你的游戏程序。具体来说,为了以兼容的方式改进这个库(即使用该库的用户不需要修改他们实现了Resizable的类),可以使用默认方法,提供setRelativeSize的默认实现:
default void setRelativeSize(int wFactor, int hFactor){setAbsoluteSize(getWidth() / wFactor, getHeight() / hFactor);}
由于接口现在可以提供带实现的方法,是否这意味着Java已经在某种程度上实现了多继承?如果实现类也实现了同样的方法,那这时会发生什么情况?默认方法会被覆盖吗?现在暂时无须担心这些,Java 8中已经定义了一些规则和机制来处理这些问题。详细的内容会在13.4节进行介绍。
你可能已经猜到,默认方法在Java 8 API中已经大量地使用了。本章已经介绍过前一章中大量使用的Collection接口的stream方法就是默认方法。List接口的sort方法也是默认方法。第3章介绍的很多函数式接口,比如Predicate、Function以及Comparator也引入了新的默认方法,比如Predicate.and或者Function.andThen(记住,函数式接口只包含一个抽象方法,默认方法是种非抽象方法)。
Java 8中的抽象类和抽象接口
那么抽象类和抽象接口之间的区别是什么呢?它们不是都能包含抽象方法和方法体的实现吗?
首先,一个类只能继承一个抽象类,但是一个类可以实现多个接口。
其次,一个抽象类可以通过实例变量(字段)保存一个通用状态,而接口是不能有实例变量的。
请应用你掌握的默认方法的知识,回答一下测验13.1的问题。
测验13.1:
removeIf这个测验里,假设你是Java语言和API的一个负责人。你收到了关于
removeIf方法的很多请求,希望能为ArrayList、TreeSet、LinkedList以及其他集合类型添加removeIf方法。removeIf方法的功能是删除满足给定谓词的所有元素。你的任务是找到用这个新方法优化Collection API的最佳途径。答案:改进Collection API破坏性最大的方式是什么?你可以把
removeIf的实现直接复制到Collection API的每个实体类中,但这种做法实际是在对Java界的犯罪。还有其他的方式吗?你知道吗,所有的Collection类都实现了一个名为java.util.Collection的接口。太好了,那么可以在这里添加一个方法吗?当然可以!你只需要牢记,默认方法是一种以源码兼容方式向接口内添加实现的方法。这样实现Collction的所有类(包括并不隶属Collection API的用户扩展类)都能使用removeIf的默认实现。removeIf的代码实现如下(它实际就是Java 8 Collection API的实现)。它是Collection接口的一个默认方法:
default boolean removeIf(Predicate<? super E> filter) {boolean removed = false;Iterator<E> each = iterator();while(each.hasNext()) {if(filter.test(each.next())) {each.remove();removed = true;}}return removed;}
