4.6 默认方法
Collection接口中增加了新的stream方法,如何能让MyCustomList类在不知道该方法的情况下通过编译?Java 8通过如下方法解决该问题:Collection接口告诉他所有的子类:“如果你没有实现stream方法,就使用我的吧。”接口中这样的方法叫作默认方法,在任何接口中,无论函数接口还是非函数接口,都可以使用该方法。
Iterable接口中也新增了一个默认方法:forEach,该方法功能和for循环类似,但是允许用户使用一个Lambda表达式作为循环体。例4-10展示了JDK中forEach的实现方式:
例4-10 默认方法示例:forEach实现方式
default void forEach(Consumer<? super T> action) {for (T t : this) {action.accept(t);}}
如果已经习惯了通过调用接口方法来使用Lambda表达式的方式,那么这个例子理解起来就相当简单。它使用一个常规的for循环遍历Iterable对象,然后对每个值调用accept方法。
既然如此简单,为何还要单独提出来呢?重点就在于代码段前面的新关键字default。这个关键字告诉javac用户真正需要的是为接口添加一个新方法。除了添加了一个新的关键字,默认方法在继承规则上和普通方法也略有区别。
和类不同,接口没有成员变量,因此默认方法只能通过调用子类的方法来修改子类本身,避免了对子类的实现做出各种假设。
默认方法和子类
默认方法的重写规则也有一些微妙之处。从最简单的情况开始来看:没有重写。在例4-11中,Parent接口定义了一个默认方法welcome,调用该方法时,发送一条信息。ParentImpl类没有实现welcome方法,因此它自然继承了该默认方法。
例4-11 Parent接口,其中的welcome是一个默认方法
public interface Parent {public void message(String body);public default void welcome() {message("Parent: Hi!");}public String getLastMessage();}
在例4-12中调用代码,我们调用默认方法,可以看到断言正确。
例4-12 在客户代码中使用默认方法
@Testpublic void parentDefaultUsed() {Parent parent = new ParentImpl();parent.welcome();assertEquals("Parent: Hi!", parent.getLastMessage());}
这时可新建一个接口Child,继承自Parent接口,代码如例4-13所示。Child接口实现了自己的默认welcome方法,凭直觉判断可知,该方法重写了Parent的方法。同样在这个例子中,ChildImpl类不会实现welcome方法,因此它自然也继承了接口的默认方法。
例4-13 继承了Parent接口的Child接口
public interface Child extends Parent {@Overridepublic default void welcome() {message("Child: Hi!");}}
此时的类继承体系如图4-4所示。

图4-4:类继承体系图
例4-14调用了该接口,最后输出的字符串自然是"Child: Hi!"。
例4-14 调用Child接口的客户代码
@Testpublic void childOverrideDefault() {Child child = new ChildImpl();child.welcome();assertEquals("Child: Hi!", child.getLastMessage());}
现在默认方法成了虚方法——和静态方法刚好相反。任何时候,一旦与类中定义的方法产生冲突,都要优先选择类中定义的方法。例4-15和例4-16展示了这种情况,最终调用的是OverridingParent的,而不是Parent的welcome方法。
例4-15 重写welcome默认实现的父类
public class OverridingParent extends ParentImpl {@Overridepublic void welcome() {message("Class Parent: Hi!");}}
例4-16 调用的是类中的具体方法,而不是默认方法
@Testpublic void concreteBeatsDefault() {Parent parent = new OverridingParent();parent.welcome();assertEquals("Class Parent: Hi!", parent.getLastMessage());}
例4-18展示了另一种情况,或许不认为类中重写的方法能够覆盖默认方法。OverridingChild本身并没有任何操作,只是继承了Child和OverridingParent中的welcome方法。最后,调用的是OverridingParent中的welcome方法,而不是Child接口中定义的默认方法(代码如例4-17所示),原因在于,与接口中定义的默认方法相比,类中重写的方法更具体(参见图4-5)。
例4-17 子接口重写了父接口中的默认方法
public class OverridingChild extends OverridingParent implements Child {}
例4-18 类中重写的方法优先级高于接口中定义的默认方法
@Testpublic void concreteBeatsCloserDefault() {Child child = new OverridingChild();child.welcome();assertEquals("Class Parent: Hi!", child.getLastMessage());}

图4-5:完整的继承体系图
简言之,类中重写的方法胜出。这样的设计主要是由增加默认方法的目的决定的,增加默认方法主要是为了在接口上向后兼容。让类中重写方法的优先级高于默认方法能简化很多继承问题。
假设已实现了一个定制的列表MyCustomList,该类中有一个addAll方法,如果新的List接口也增加了一个默认方法addAll,该方法将对列表的操作代理到add方法。如果类中重写的方法没有默认方法的优先级高,那么就会破坏已有的实现。
