Java 8th 函数式编程:默认接口方法

Java 8th 可以看做是 java 版本更新迭代过程中变化最大的几个版本之一(与时俱进,方能不灭),但是经过这么多年的发展和迭代,java 的源码俨然已是一个庞然大物,要在这样庞大的体积上大动干戈必定不易。所以当第一次看到默认接口方法的时候,我第一感觉就是这是设计人员在填自己之前挖的坑。

从前几篇的讲解中我们知道 8th 在现有的接口上添加了许多方法,比如 List 的 sort(Comparator<? super E> c) 方法。如果按照 8th 之前接口的设计思路,当给一个接口添加方法声明的时候,实现该接口的类都必须为该新添加的方法添加相应的实现(或将自己设置为抽象类)。考虑兼容性这样是不可取的,所以说这是一个坑,而新的特性又要求不得不为接口添加一些新的方法,为了兼得鱼和熊掌,设计人员提出了默认接口方法的概念。

一. 默认接口方法的定义

默认接口方法的定义很简单,只要在接口的方法定义前添加一个 default 关键字即可,如下:

1
2
3
4
5
6
7
8
9
10
public interface A {

/**
* 默认方法定义
*/
default void method() {
System.out.println("This is a default method!");
}

}

当我们定义了一个默认接口方法之后,所有实现该接口的子类都间接持有了该方法。或许你会和我一样觉得接口和抽象类越来越像了,确实,不过它们之间还是有如下差别:

  1. 一个类只能继承一个类,但是可以实现多个接口
  2. 抽象类可以定义变量,而接口却不能

除了上面提及到的区别,接口方法还具有如下优点:

  1. 对于一些不是每个子类都需要的方法,我们给它一个默认实现,从而避免子类中的无意义实现(一般我们都会直接 throw new UnsupportedException()
  2. 默认方法为 java 的多重继承机制提供了新途径(虽然我们只能继承一个类,但是我们可以实现多个接口啊,现在接口也可以定义默认方法了)

二. 冲突及其解决方法

一个类可以实现多个接口,当一个类实现了多个接口,而这些接口中存在两个或两个以上方法签名相同的默认方法时就会产生冲突,8th 定义如下三条原则以解决冲突:

  1. 类或父类中显式声明的方法,其优先级高于所有的默认方法
  2. 如果 1 规则失效,则选择与当前类距离最近的具有具体实现的默认方法
  3. 如果 2 规则也失效,则需要显式指定接口

下面通过几个例子加以说明:

  • 例1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public interface A {
/**
* 默认方法定义
*/
default void method() {
System.out.println("A's default method!");
}
}

public interface B extends A {
/**
* 默认方法定义
*/
default void method() {
System.out.println("B's default method!");
}
}

public class C implements A, B {

public static void main(String[] args) {
new C().method();
}
}

// 输出:B's default method!

此处因为接口 B 相对于 A 距离 C 更近,同时 B 的 method 是一个具体的默认实现,依据规则 2,所以此处实际上调用的是接口 B 的默认方法。

  • 例2
1
2
3
4
5
6
7
8
9
10
11
12
public class D implements A {
}

public class C extends D implements A, B {

public static void main(String[] args) {
new C().method();
}

}

// 输出:B's default method!

例 2 在原有接口 A 和 B 的基础上添加了一个实现接口 A 的类 D,然后类 C 继承于 D,并实现 A 和 B 接口,此处虽然 C 离 D 更近,但因为 D 的具体实现在 A 中,所以 B 中的默认方法还是距离最近的默认实现,依据规则 2,此处实际上调用的是 B 的默认方法。

  • 例3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// A接口不变

public interface B {

/**
* 默认方法定义
*/
default void method() {
System.out.println("B's default method!");
}

}

public class C implements A, B {

@Override
public void method() {
// 必须显式指定
B.super.method();
}

public static void main(String[] args) {
new C().method();
}

}

例 3 中接口 B 不再继承自接口 A,所以此时 C 中调用默认方法 method() 距离接口 A 和 B 的具体实现距离相同,编译器无法确定,所以报错,此时需要显式指定:B.super.method()