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