在Java8发布之际,有件事情就显得很是重要,即在不破坏java现有实现架构的状况下能往接口里增长新方法。引入Default方法到Java8,正是为了这个目的:优化接口的同时,避免跟现有实现架构的兼容问题。看下面例子:html
1 List<?> list = ... 2 list.forEach(...);// Lambda code goes here
这样,即便咱们把Lambda表达式引入到java8中,可是由于不能牺牲向后兼容,而不能够把Lambda表达式和标准集合类库结合使用的话,会真的让人很泄气。上面的foreach方法既没有在java.util.List中声明,也没有在java.util.Collection中声明。(若是要使上面代码生效)容易想到的方案是在现有的接口中新增foreach方法,并在JDK中必要的地方实现foreach。然而,一经发布,要想在某个接口中增长方法,而不修改现该接口现有的实现类,这是不可能作到的。java
为了解决上述问题,引入了一个新的概念,即虚拟扩展方法(Virtual extension methods),一般也称之为 defender 方法,它目前能够添加到接口中,为声明的方法提供默认的实现。api
简单地说,Java接口如今能够有非抽象方法了。Default 方法带来的好处是,往接口新增一个Default 方法,而不破坏现有的实现架构。架构
尽管如此,Default 方法不适合过多使用,可是对于Java集合API的优化升级,并达到无缝地结合Lambda表达式来讲,Default 方法是相当重要的特性。函数
让咱们从最简单可行的例子开始:下面代码定义了接口A,以及实现了接口A的Clazz类:优化
1 public interface A { 2 default void foo(){ 3 System.out.println("Calling A.foo()"); 4 } 5 } 6 public class Clazz implements A {}
上例的客户端代码以下:即便Clazz中没有实现foo(),代码也能编译经过。foo()的默认实如今A接口中给出。ui
1 Clazz clazz= new Clazz(); 2 clazz.foo();// Calling A.foo()
让咱们用上一个例子来解释上述状况,代码以下所示:当你们第一次据说default方法,一般会问:“若是一个类实现了两个接口,而这两个接口中各自定义了一个同名的default方法,会怎么样?”this
1 public interface A { 2 default void foo(){ 3 System.out.println("Calling A.foo()"); 4 } 5 } 6 public interface B { 7 default void foo(){ 8 System.out.println("Calling B.foo()"); 9 } 10 } 11 public class Clazz implements A, B {}
java: class Clazz inherits unrelated defaults for foo() from types A and B上面代码编译失败,报错以下:spa
为了修复错误,在Clazz中,咱们手工地覆写掉冲突的方法来处理这个问题,以下所示:.net
1 public class Clazz implements A, B { 2 public void foo(){} 3 }
1 public class Clazz implements A, B { 2 public void foo(){ 3 A.super.foo(); 4 } 5 }
Default方法的实例能够在JDK8中找到。回到以前集合API的forEach 的例子,咱们能够在接口java.lang.Iterable中找到forEach 的默认实现:但是,若是咱们就想调用接口A中默认实现的方法foo(),而不用本身实现的,该怎么办?那就要像下面这样使用A#foo()才行。
1 @FunctionalInterface public interface Iterable{ 2 Iterator iterator(); 3 4 default void forEach(Consumer<?super T> action){ 5 Objects.requireNonNull(action); 6 for(T t: this){ 7 action.accept(t); 8 } 9 } 10 }
上面的forEach方法使用函数接口java.util.function.Consumer做为参数,该参数使咱们能传递一个Lambda表达式或者方法引用到forEach中,以下所示:
1 List<?> list = ... 2 list.forEach(System.out::println);
方法调用
咱们来看看其实是如何调用default方法的。
从客户端代码角度来看,default方法只不过都是初始化的抽象方法。所以default方法也叫-虚拟扩展方法。若是出现上例中的那个类,该类实现了带default方法的接口,那么调用default方法的客户端代码会在调用端生成invokeinterface 。以下所示
1 A clazz= new Clazz(); 2 clazz.foo();// invokeinterface foo() 3 4 Clazz clazz= new Clazz(); 5 clazz.foo();// invokevirtual foo()
下面是javap的输出。以防出现上述的default方法冲突问题,因此咱们重写那个default方法,并代理两个接口之中某一个的default方法的调用,具体代码请见下面。invokespecial意思是指 咱们将调用专门的实现逻辑。
1 public class Clazz implements A, B { 2 public void foo(){ 3 A.super.foo();// invokespecial foo() 4 } 5 }
下面是javap的输出。
public void foo();
Code:
0: aload_0
1: invokespecial #2 // InterfaceMethodA.foo:()V
4: return
如上面所示的,invokespecial 指令用来调用接口中定义的方法foo()。这从字节码的角度看也是新东西,由于先前你进行方法调用,仅是经过指向父类的super而不是指向父接口的。
Default方法加入到java中,这是引人关注的事情。Default方法能够认为是Lambda表达式和JDK类库之间的桥梁。引入Default方法的主要目的是为了升级标准JDK接口,另外也是为了咱们最终能在Java8中顺畅使用Lambda表达式。