就从Java8开始吧(六)接口默认方法和静态方法
- content {:toc}
# 闲话
这一节我们聊一聊接口的默认方法和静态方法。一切要从接口开始说起。接口是泛指一种实体把自己提供给外界的抽象化物,它最明显的特征是把内部操作和实现方式隐藏,只保留与外部最直接最简单的沟通。举个例子,我们每天要吃饭,通过食物来摄取营养,这时我们和食物之间的接口就是我们的嘴巴,我们只需要告知外界我们通过嘴巴吃食物,至于在胃里怎么消化,在肠里怎么吸收,都属于身体的实现细节,没有必要暴露给外界。
在Java语言中,接口是一系列抽象方法的集合,通过inferface关键字来声明。类通过实现(implements)接口的方式,来实现接口中的抽象方法。在Java8之前,接口的定义非常地“规范”,只能有public的抽象方法和常量组成。也就是说,接口中的方法是不能够在接口中直接实现的,只能在接口的实现类里来完成接口的实现。这样虽然最大限度地保证了接口的规范性,但是很多情况下降低了接口的可扩展性和便利性。比如在Java8中引入了lambda表达式的概念,我们要给Collection接口中添加一个forEach方法,如果接口不允许实现自己的方法,那就意味着Collection所有的实现类都需要添加forEach方法的实现,也就是说在Java8出现之前编写的所有Collection实现类如果不修改代码都会编译报错,即修改后的接口失去了向前兼容性。另外,如果接口中的某个方法在大多数情况下拥有相同的实现,那以在接口的每一个实现类中都需要编写相同的方法实现,这也就违背了DRY(don't repeat yourself)原则。
基于上述原因,在Java8中引入了一个新的概念——虚拟扩展方法,也就是通常说的defender方法,可以将其加入到接口中,这样可以提供方法的默认实现。实现类可以不重写默认方法,因此新添加的默认方法不会破坏接口的现有实现。因此可以这么说,接口的默认方法是lambdas表达式和JDK库之间的桥梁,它使得标准JDK接口得以进化。
再来聊一聊静态方法。过去版本的jdk中,为什么没有接口静态方法?关于这一点,可以参见stackoverflow上的讨论 (opens new window)。根据官方文档的介绍,过去接口中没有静态方法并非强烈的技术原因导致的。设计师们本来提议在Java7中加入接口静态方法,但是由于不可预见的复杂性(unforeseen complications),将这一项设计推迟到了Java8中。接口静态方法可以改善JDK库的设计,例如Collections类中关于List操作的方法,本来可以放置在List接口中。
# API干货
接口的默认方法使用default关键字修饰,如:
public interface I {
default void method1(){
System.out.println("调用I.method1()方法")
}
default void method2(){
System.out.println("调用I.method2()方法")
}
}
public class Clazz implements I {
// 类中可以选择性重写接口的默认方法
@Override
public void method2(){
System.out.println("调用Clazz.method2()方法")
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
默认方法的设计有可能引发一定的问题,一个类在实现多个接口时,如果接口中有两个默认方法的方法签名相同,会发生什么情况?让我们先来看一下示例代码:
public interface I {
default void hello(){
System.out.println("调用I.hello()方法")
}
}
public interface J {
default void hello(){
System.out.println("调用J.hello()方法")
}
}
public class Clazz implements I, J{
}
2
3
4
5
6
7
8
9
10
11
12
13
这时编译器分辨不出来到底应该继承哪个hello方法,于是编译器会报错:inherits unrelated defaults for hello()from types I and J。那么怎么解决这个问题呢?很简单,只要显式地重写hello方法即可:
public class Clazz implements I, J{
public void hello(){
System.out.println("调用Clazz.hello()方法")
}
}
2
3
4
5
还有更复杂的”菱形调用“,大家可以自行翻阅API。不刻意设计的情况下,一般是不会出现这种问题的。
静态方法的使用比较简单,和类中的静态方法没有什么区别:
public interface A {
static void hello() {
System.out.println("调用A.hello()静态方法")
}
}
public class Test {
public static void main(String[] args) {
A.hello();
}
}
2
3
4
5
6
7
8
9
10
注意static和default关键字不能同时使用,即一个接口方法不可能既是静态的又是默认方法。
# 设计思路
几天前(2018年9月25日),Oracle发布了Java11。从Java8以来的几个版本的改动来看,Java的设计越来越务实,也越来越灵活。接口的默认方法和静态方法就是一个例子。从以前版本的观点来看,默认方法和静态方法是不太规范的东西。现在这些”不规范“特性的引入,也说明Java越来越包容,越来越迎合这个时代。默认方法的出现使得引入lambda表达式畅通无阻,使得Java最终加入了函数式编程的大军,大大简化了程序设计,提高了程序的可读性。接口静态方法的引入,使得接口设计的内聚性更强,避免了在设计中引入过多冗余的类。
# 最佳实践
编程不适合守旧者。既然有新东西,我们就应该果断学习和适应,果断拿来用。
利用接口默认方法我们可以来扩展旧有接口,也可以将通用的方法实现提炼到接口中。我们可以把原本适合设计成接口的抽象类重新设计为接口。
利用接口静态方法我们可以把属于接口中的静态方法还给接口,去掉外部冗余的方法和类。
# 练习与思考
- 学习集合框架源码中接口default方法的使用。
- 学习编写接口静态方法和默认方法的demo。
- 01
- 深入浅出java web (二):简述http协议02-12
- 02
- 深入浅出java web(一):从根说起——servlet02-08
- 03
- 提高开发效率的奇技(二)Intellij IDEA的进阶应用10-09