Java8新增特性,能够为接口中添加默认方法,实现这个接口的全部类都会继承这个方法,这样看起来,接口和类的界限就有点不明显了,同时也会带来多继承,菱形问题。这样设计的初衷是什么?java
重所周知,java8开始支持lambda表达式,能够把函数当作参数传递,最明显的lambda表达式应用场景莫过于对collection的每个元素应用lambda。若是想为Collection实现lambda表达式:list.forEach(…);
// 这就是lambda代码
程序员
首先想到的是为Collection的父接口iterator添加抽象方法forEach()。然而,对于已经发布的版本,是无法在给接口添加新方法的同时不影响已有的实现。若是添加了,那么全部的iterator()实现类都要重写这个方法,若是只是jre本身的类库还好说,大量的第三方类库都使用到了java的优秀集合框架,若是都要重写,这是不合理的。框架
所以,若是在Java 8里使用lambda的时候,由于向前兼容的缘由而不能用于collection库,那有多糟糕啊。ide
因为上述缘由,引入了一个新的概念。虚拟扩展方法,也即一般说的defender方法, 如今能够将其加入到接口,这样能够提供声明的行为的默认实现。函数
简单的说,Java的接口如今能够实现方法了。默认方法带来的好处是能够为接口添加新的默认方法,而不会破坏接口的实现。ui
以前:Java接口纯粹是契约的集合,是一种程序设计的表达方式。从数据抽象的角度看,可以在不定义class的同时又能够定义type,将是程序设计中强大而有用的机制。Java接口就是这些纯粹的接口组成的数据抽象。Java接口只可以拥有抽象方法,它不涉及任何实现,也不能建立其对象(这一点和抽象类一致)。this
多重继承模型致使额外的复杂性,其中最著名的是钻石问题或者叫“讨嫌的菱形派生”(Dreadful Diamond onDerivation、DDD)。为何Java接口可以避免多继承的复杂性,关键在于它仅仅包含abstract方法。然而从设计的角度看,Java接口放弃了多继承的内在/固有目标,而显得是一个权宜之计。spa
如今:Java8以前,接口不能升级。由于在接口中添加一个方法,会致使老版本接口的全部实现类的中断。λ表达式做为核心出现,为了配合λ表达式,JDK中Collection库须要添加新的方法,如forEach(),stream()等,因而引入了默认方法(defender methods,Virtual extension methods)。它是库/框架设计的程序员的后悔药。对于之前的遗留代码,你们都不知道有这个新方法,既不会调用,也不会去实现,如同不存在;编写新代码的程序员能够将它视为保底的方法体。类型层次中任何符合override规则的方法,优先于默认方法,由于遗留代码可能正好有一样的方法存在。.net
默认方法,理论上抹杀了Java接口与抽象类的本质区别——前者是契约的集合,后者是接口与实现的结合体。固然,语法上二者的差异和之前同样。这就须要程序员来自觉维护二者的本质区别,把默认方法做为库、框架向前兼容的手段。设计
默认方法的一个好处:多继承的著名的是钻石问题(The Diamond Problem )再次须要关注。于是使之前某些人认为的“为了解决多继承问题而引入接口机制”的说法变成明显的错误——之前也是错误的认识。
默认方法的声明很简单,直接在接口中把方法声明为default,以后再写方法的实现便可。这样全部的实现类都会继承这个方法,问题是他带来的多继承问题如何解决?
Iterable中的默认方法:
default void forEach(Consumer<? super T> action) { Objects.requireNonNull(action); for (T t : this) { action.accept(t); } }
下面是一个简单的默认方法实现:
public interface A { default void foo(){ System.out.println("Calling A.foo()"); } } public class Clazz implements A { } Clazz clazz = new Clazz(); clazz.foo(); // 调用A.foo()
下面是一个多继承:
public interface A { default void foo(){ System.out.println("Calling A.foo()"); } } public interface B { default void foo(){ System.out.println("Calling B.foo()"); } } public class Clazz implements A, B { }
这段代码不能编译 有如下缘由:
java:class Clazz 从types A到B给foo()继承了不相关的默认值
为了修复这个,在Clazz里咱们不得不手动解决经过重写冲突的方法,或者调用某个接口中的方法:
public class Clazz implements A, B { public void foo(){} } public class Clazz implements A, B { public void foo(){ A.super.foo(); } }
钻石问题
状况一:接口IA有子接口IB一、IB2,而类C implements IB1,IB2
package java8; public class C implements IB1, IB2{ public static void main(String[] a) { new C().m(); } }
(1)若是仅仅A定义默认方法m(),执行IA的默认方法;
(2)若是只有IB一、IB2其中一个override IA定义的m(),调用“最具体接口”默认方法;
(3)若是IB一、IB2都override IA定义的m(),而C不提供本身的方法体,则编译错误!由于 “最具体接口”默认方法有两个。此时,C若Override m(),能够消去编译错误。
(4)类C提供本身的方法体时,能够提供本身的代码,也能够指定调用C implements 的直接父接口的默认方法
小结:多个接口提供默认方法,则“最具体接口”默认方法胜出,可是不得出现多个“最具体接口”。
状况二:接口IA(或IB1)定义了默认方法m(),类A1相同的方法m(),类C是它们的子类型。
若是类A1提供了实现,按照a simple rule: “the superclass always wins.”),父类的方法 被调用;
若是类A1不提供实现,即A1中m()为抽象方法,仍然按照the superclass always wins.类C须要override m(),给出本身的实现。不然,要么 C声明为抽象类,要么编译错误。
小结:父类有默认方法的等价物,则默认方法如同不存在。
默认方法是对Java语言的有趣补充 – 你能够把他们看作是lambdas表达式和JDK库之间的桥梁。默认表达式的主要目标是使标准JDK接口得以进化,而且当咱们最终开始使用Java 8的lambdas表达式时,提供给咱们一个平滑的过渡体验。谁知道呢,也许未来咱们会在API设计中看到更多的默认方法的应用。
http://www.oschina.net/translate/java-8-explained-default-methods