目录
- 什么是默认方法
- 编写默认方法
- 解决继承冲突
什么是默认方法
传统上来说,接口只负责声明方法约定,接口的实现类必须为接口中定义的每个方法提供实现。但是,一旦接口设计者需要更新接口,向其中加入新的方法,这种方式就会出现问题。因为接口设计者并不能控制接口实现者,这就意味着一旦接口发生变更,则需要所有的接口实现者修改其实现类。比如 JAVA8
在已有的 API
基础上增加了大量方法,这种修改带来的问题也就愈加严重。为了解决这一问题,JAVA8
引入了一种新的机制。 JAVA8
中的接口在声明方法的同时也可以提供实现:
- 允许接口内声明静态方法
- 引入默认方法,通过默认方法可以指定接口方法的默认实现,也就是说接口能提供方法的具体实现,实现类可以自动地继承接口的默认实现
静态方法及接口
同时定义接口以及工具辅助类(companion class
)是 Java
语言常用的一种模式,工具类定义了与接口实例协作的很多静态方法。比如,Collections
就是处理 Collection
对象的辅助类。由于静态方法可以存在于接口内部,代码中的这些辅助类就没有了存在的必要,你可以把这些静态方法转移到接口内部。
编写默认方法
默认方法由 default
修饰符修饰,比如,设计一个集合类接口 Sized
,在其中定义一个抽象方法 size()
,以及一个默认方法 isEmpty()
,这样任何实现了 Sized
接口的类将会自动继承 isEmpty()
的实现。
public interface Sized {
int size();
default boolean isEmpty() {
return size() == 0;
}
}
由于接口现在可以提供带实现的方法,是否这意味着 Java
已经在某种程度上实现了多继承?
抽象类与接口的区别
- 首先,一个类只能继承一个抽象类,但是一个类可以实现多个接口。
- 其次,一个抽象类可以通过实例变量(字段)保存一个通用状态,而接口是不能有实例变量的。
解决继承冲突
一个类只能继承一个父类,但是可以实现多个接口,随着默认方法的引入,有可能出现一个类继承了多个方法而它们使用的却是同样的函数签名。这种情况下,选择使用哪一个函数?
public interface A {
default void hello() {
System.out.println("Hello from A");
}
}
public interface B extends A {
default void hello() {
System.out.println("Hello from B");
}
}
public class C implements B, A {
public static void main(String... args) {
new C().hello();
}
}
解决问题的三条规则
- 1、类中的方法优先级最高。类或父类中声明的方法的优先级高于任何声明为默认方法的优先级。
- 2、如果无法依据第一条进行判断,那么子接口的优先级更高:函数签名相同时,优先选择拥有最具体实现的默认方法的接口,即如果
B
继承了A
,那么B
就比A
更加具体。 - 3、最后,如果还是无法判断,继承了多个接口的类必须通过显式覆盖和调用期望的方法,显式地选择使用哪一个默认方法的实现。
举例说明
以上面的代码为例,C
类同时实现了 B
接口和 A
接口,这两个接口都定义了 hello
默认方法。另外,B
继承自 A
。
依据规则 2,应该选择的是提供了最具体实现的默认方法的接口,由于 B
比 A
更具体,所以选择执行 B
的 hello
方法,输出结果为 Hello from B
。
将类 C
改装一下,继承类 D
,如下代码所示
public class D implements A {
public void hello() {
System.out.println("Hello from D");
}
}
public class C extends D implements B {
public static void main(String... args) {
new C().hello();
}
}
UML图
依据规则 1,类中声明的方法具有更高的优先级,所以输出结果为 Hello from D
再次改装,类 D
不重写默认方法
public class D implements A { }
public class C extends D implements B {
public static void main(String... args) {
new C().hello();
}
}
UML图
依据规则 1,类中声明的方法具有更高的优先级,所以类 D
具备更高的优先级,但是 D
并未覆盖 hello
方法,可是它实现了接口 A
,所以它拥有接口 A
的默认方法。依据规则 2,说如果类或者父类没有对应的方法,那么就应该选择提供了最具体实现的接口中的方法。因此,编译器会在接口 A
和接口 B
的 hello
方法之间做选择,由于 B
更加具体,所以输出结果为 Hello from B
。
如果类 D
是接口呢?结果又会是怎么样?
public interface D extends A { }
public class C implements B, D {
public static void main(String... args) {
new C().hello();
}
}
UML图
类 D
并未覆盖 hello
方法,拥有的还是接口 A
的默认方法,同上例一样,依据规则 2,选择执行 B
的 hello
方法,输出结果为 Hello from B
。
如果类 D
重写默认方法 hello
public interface D extends A {
default void hello() {
System.out.println("Hello from D");
}
}
public class C implements B, D {
public static void main(String... args) {
new C().hello();
}
}
UML图
真正产生了接口方法的冲突,此时规则 2 也无法判断哪一个接口更加具体,此时,程序将会编译不通过,抛出 C inherits unrelated defaults for hello() from types D and B
,此时,必须在类 C
中进行显式调用。
public class C implements B, D {
public void hello() {
B.super.hello();
}
public static void main(String... args) {
new C().hello();
}
}
自已动手:几乎完全一样的函数签名,如下代码是否可以编译通过,如果可以,输出结果是什么?
public interface A {
default Number getNumber() {
return 10;
}
}
public interface B {
default Integer getNumber() {
return 42;
}
}
public class C implements B, A {
public static void main(String... args) {
System.out.println(new C().getNumber());
}
}
答案:无法编译通过