翎野君/文
引言
默认方法的引入就是为了,以兼容的方式,解决像 Java API这样的类库,演进迭代问题。
理解演进迭代
为了理解为什么一旦API发布之后,它的演进就变得非常困难,我们假设你是一个Github上的开源作者,兴致勃勃的写了一个开源项目,然后放到了Github上面。
没过多久你的项目就被其他用户Fork到本地,然后开始使用了起来,并且在项目中对你发布的一些接口进行了实现。
发布API几个月之后,你突然意识到接口中遗漏了一些功能。需要调整原来的接口,在其中新增方法,这样的话接口的易用性会更好。
不过,事情并非如此简单!你要考虑已经使用了你接口的用户,他们可能已经按照自身的需求实现了你的接口,倘若你更新了接口的API并重新进行了发布,那么所以实现了你的接口的地方,都需要进行改动。
简而言之,向接口添加方法是诸多问题的罪恶之源;一旦接口发生变化,实现这些接口的类往往也需要更新,提供新添方法的实现才能适配接口的变化。如果你对接口以及它所有相关的实现有完全的控制,这可能不是个大问题。但是这种情况是极少的。
这就是引入默认方法的目的:它让类可以自动地继承接口的一个默认实现。
概述
默认方法是Java 8中引入的一个新特性,希望能借此以兼容的方式改进API。现在,接口包含的方法签名在它的实现类中也可以不提供实现。那么,谁来具体实现这些方法呢?实际上,缺失的方法实现会作为接口的一部分由实现类继承(所以命名为默认实现),而无需由实现类提供。
默认方法由default修饰符修饰,并像类中声明的其他方法一样包含方法体。比如,你可以像下面这样在集合库中定义一个名为Sized的接口,在其中定义一个抽象方法size,以及一个默认方法isEmpty:
public interface Sized {
int size();
default boolean isEmpty() {
return size() == 0;
} }
这样任何一个实现了Sized接口的类都会自动继承isEmpty的实现。
你肯定碰到过这种情况,一个类实现了接口,不过却将一些实现方法进行留白,没有实现。
我们以Iterator接口为例来说。Iterator接口定义了hasNext、next,还定义了remove方法。Java 8 之前,由于用户通常不会使用该方法,remove方法常被忽略。因此,实现Interator接口的类通常会为remove方法放置一个空的实现,这些都是没有意义毫无用处的代码。采用默认方法之后,你可以为这种类型的方法提供一个默认的实现,这样实体类就无需在自己的实现中显式地提供一个空方法。比如,在Java 8中,Iterator接口就为remove方法提供了一个默认实现。
interface Iterator<T> { boolean hasNext();
T next();
default void remove() {
throw new UnsupportedOperationException();
}
}
通过这种方式,你可以减少无效的模板代码。实现Iterator接口的每一个类都不需要再声明一个空的remove方法了,因为它现在已经有一个默认的实现。
下面是Java API中对ArrayList类的定义:
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
}
这个例子中ArrayList继承了一个类,实现了四个接口。因此ArrayList实际是 五个类型的直接子类,分别是:AbstractList,List,RandomAccess,Cloneable,Serializable。所以,在某种程度上,我们就有了类型的多继承。
由于Java 8中接口方法可以包含实现,类可以从多个接口中继承它们的行为(即实现的代码)。 让我们从一个例子入手,看看如何充分利用这种能力来为我们服务。
public interface MoveService {
void run();
default void flash() {
System.out.println("闪现!!!");
}
}
public interface SkillService {
void q();
void w();
void e();
default void r() {
System.out.println("默认大招:伤害100点");
}
}
public class Shooter implements MoveService, SkillService {
@Override
public void run() {
System.out.println("寒冰射手 走~~");
}
@Override
public void q() {
System.out.println("寒冰 q");
}
@Override
public void w() {
System.out.println("寒冰 w");
}
@Override
public void e() {
System.out.println("寒冰 e");
}
public void r(){
System.out.println("寒冰 伤害100点!!同时冰冻对方5s!!!");
}
public static void main(String[] args) {
new Shooter().r();
}
}
方法冲突
我们知道Java语言中一个类只能继承一个类,但是一个类可以实现多个接口。 随着默认方法在Java 8中引入,有可能出现一个类继承了多个方法而它们使用的却是同样的函数签名。这种情况下,在一个类中使用父类的默认方法,这样会有冲突吗,没有的话,那会选择哪一个呢?
如果一个类使用相同的函数签名从多个地方(比如另一个类或接口)继承了方法,通过三条规则可以进行判断。
public interface PlayerService {
default void stop() {
System.out.println("播放器--停止!!!");
}
}
public class SonyPlayerServiceImpl implements PlayerService {
public void stop() {
System.out.println("Sony播放器--停止!!!");
}
public static void main(String[] args) {
new SonyPlayerServiceImpl().stop();
}
}
public interface PlayerService {
default void showLyric() {
System.out.println("PlayerService : show lyric");
}
}
public interface RecordService extends PlayerService{
default void showLyric() {
System.out.println("RecordService : show lyric");
}
}
public class Question1 implements RecordService {
public static void main(String[] args) {
new Question1().showLyric();
System.out.println("应该选择的是提供了最具体实现的默认方法的接口。由于RecordService比PlayerService更具体,所以应该选择由于RecordService比PlayerService更具体的showLyric方法。");
}
}
public class Question2 implements PlayerService,RecordService {
@Override
public void showLyric() {
PlayerService.super.showLyric();
RecordService.super.showLyric();
}
public static void main(String[] args) {
new Question2().showLyric();
}
}
小结
作者:翎野君 博客:https://www.cnblogs.com/lingyejun/
本篇文章如有帮助到您,请给「翎野君」点个赞,感谢您的支持。