什么是默认方法
在传统的Java程序中,实现接口的方式是通过Implements把接口中的每一个方法提供一个实现,或者从父类继承他的实现。
然而,在实际开发过程中,往往需要更新接口,向其中加入新的方法。这样的方式就会出现问题。
Java8引入了一种新的方式——默认方法。这种方式支持声明方法的同时提供实现。通过两种方式可以完成上面加粗的操作,一是通过Static在接口中声明静态方法。而是通过默认方法。
默认方法顾名思义,可以指定接口方法的默认实现,如果不显示地提供该方法的具体实现,就会自动继承默认的实现。这种机制可以平滑的进行接口的优化和演进。
实际上,到目前为止你已经使用了多个默认方法。两个例子就是你前面已经见过的List接口中的sort,以及Collection接口中的stream。
default void sort(Comparator<? super E> c){
Collections.sort(this, c);
}
请注意返回类型之前的新default
修饰符。通过它,我们能够知道一个方法是否为默认方法。
这里sort方法调用了Collections.sort方法进行排序操作。
default Stream<E> stream() {
return StreamSupport.stream(spliterator(), false);
}
,默认方法的引入就是为了以兼容的方式解决像Java API这样的类库的演进问题的。如下图所示:
不断演进的API
下面,我们将使用一些代码来模拟API的演进。
初始版本API
Resizable接口的最初版本提供了下面这些方法:
public interface Resizable extends Drawable{
int getWidth();
int getHeight();
void setWidth(int width);
void setHeight(int height);
void setAbsoluteSize(int width, int height);
}
用户是这样实现的:
public class Ellipse implements Resizable {
...
}
public class Rectangle implements Resizable {
...
}
public class Square implements Resizable {
...
}
他实现了一个处理各种Resizable形状(包括Ellipse)的游戏:
public class Game{
public static void main(String...args){
List<Resizable> resizableShapes =
Arrays.asList(new Square(), new Rectangle(), new Ellipse());
Utils.paint(resizableShapes);
}
}
public class Utils{
public static void paint(List<Resizable> l){
l.forEach(r -> {
r.setAbsoluteSize(42, 42);
r.draw();
});
}
}
第二版API
库上线使用几个月之后,你收到很多请求,要求你更新Resizable的实现,让Square、Rectangle以及其他的形状都能支持setRelativeSize方法。
public interface Resizable extends Drawable{
...
//新增方法
void setRelativeSize(int wFactor, int hFactor);
}
上面这张图,为Resizable接口添加新方法改进API。再次编译应用时会遭遇错误,因为它依赖的Resizable接口发生了变化。
更新已发布API会导致后向兼容性问题。这就是为什么对现存API的演进,比如官方发布的Java Collection API,会给用户带来麻烦。当然,还有其他方式能够实现对API的改进,但是都不是明智的选择。比如,你可以为你的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);
}