目录

  1. 一. 引入默认接口方法的背景
  2. 二. 默认接口方法的定义
  3. 三. 冲突及其解决方法

一. 引入默认接口方法的背景

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

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

这样说来,默认接口方法似乎是为api的设计人员而开发的,离我们普通开发人员还有些距离,这样想有点图森破啦,虽然我们不用去设计jdk,但是我们在日常的开发过程中还是会有提供api给别的业务方调用的需求,当我们在更新我们api的时候,就可以采用默认方法来提供更加高级的功能,同时保持兼容性。

二. 默认接口方法的定义

默认接口方法的定义很简单,只要在接口的方法定义前添加一个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. 一个类只能继承一个类,但是可以实现多个接口
2. 抽象类可以定义变量,而接口却不能

抽象除了解决了我们上面提及到的问题,还具有如下好处:

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

三. 冲突及其解决方法

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

1
2
3
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
27
28
29
30
31
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()