什么是默认方法
Java 8之前接口只能有抽象方法,而Java 8中的接口现在允许在接口内声明方法,同时提供实现。在接口中提供实现有两种方式,一种是静态方法,这个和普通的静态方法类似;另一种是新引入的默认方法。默认方法通过使用 "default" 修饰方法,让接口的方法提供默认实现,实现接口的子类,如果不显式地提供该方法的实现,就会自动继承默认的实现。
为啥要引入默认方法
如果看了定义,你会发现接口会越来越像抽象类了,但是类是单继承的,接口可以是多实现的。默认方法的主要目标是类库的设计者,它的引进是为了以兼容的方式解决像JAVA API这样的类库的演进问题。
一些类库的接口在初始阶段设计的不够全面,或者类库更新迭代需要加入新的功能。如果在接口中加入新的方法,那么意味着这些接口的实现类就要跟着更新,添加对应的方法实现。但是类似于Collection等Java接口的子类,JDK 开发者并不能控制所有子类的实现(也不可能实现,例如自己写了个继承Collection的工具类)。为了在接口中增加方法为不需要子类同时更新,Java 8就引入了默认方法。
我们在Collections会发现很多关于Collection的静态方法,由于现在接口可以定义静态方法了,这些类也没有了存在的必要了。
例如,List接口中的sort方法就是默认方法,是在Java 8引入的
default void sort(Comparator<? super E> c) {
Object[] a = this.toArray();
Arrays.sort(a, (Comparator) c);
ListIterator<E> i = this.listIterator();
for (Object e : a) {
i.next();
i.set((E) e);
}
}
当我们使用ArrayList,LinkedList,或者自己实现List接口的时候,就可以使用sort方法。
解决冲突的规则
随着默认方法的加入,方法的实现可以从多个接口继承过来了。那么子类应该使用哪个方法呢?这个有点像C++的菱形问题。解决这个问题有3个原则,依次进行使用
-
类中的方法优先级最高。由于只能继承一个类,所以不会有冲突。
-
如果第一条不能确定,那么子接口的优先级更高,优先选择拥有最具体实现的默认方法的接口。例如B接口继承了A接口,C子类同时继承B和A接口,那么会从B继承签名冲突的方法。
-
最后,如果还不能解决,那就要显式覆盖和调用期望的方法。
- 例子1
interface A {
default void hello() {
System.out.println("Hello from A !");
}
}
class B implements A {
public void hello() {
System.out.println("Hello from B !");
}
}
class C extends B implements A {
public static void main(String[] args) {
new C().hello();
}
}
运行C的main方法会输出 Hello from B !
使用了规则一,优先使用父类的方法。
- 例子2
interface A {
default void hello() {
System.out.println("Hello from A !");
}
}
interface B extends A {
default void hello() {
System.out.println("Hello from B !");
}
}
class C implements B,A {
public static void main(String[] args) {
new C().hello();
}
}
运行C的main方法会输出 Hello from B !
C并没有继承其他类得到hello方法,第一个规则不能判断;使用第二个规则,选用最具体实现的方法。上面A,B都有hello方法,但是B重写了A的hello方法,拥有更加具体的实现。
- 例子3
interface A {
default void hello() {
System.out.println("Hello from A !");
}
}
interface B {
default void hello() {
System.out.println("Hello from B !");
}
}
class C implements A, B {
public static void main(String[] args) {
new C().hello();
}
@Override
public void hello() {
B.super.hello();
}
}
上面这个例子也会输出 Hello from B !
C没有从其他类继承hello相同签名的方法,不能使用规则1判断;C实现了A,B两个接口,但是A,B是相同优先级的,不能使用规则2;那么就只能显示指定使用哪个接口的方法了。
通过重新hello方法,显示调用了B接口的方法 B.super.hello();
- 例子4,“菱形问题”
interface A {
default void hello() {
System.out.println("Hello from A !");
}
}
interface B extends A { }
interface D extends A { }
class C implements B, D {
public static void main(String[] args) {
new C().hello();
}
}
这里会输出Hello from A !
使用规则2,B和D的优先级一样,但是B和D的hello的方法都是来自A接口,只有一个实现,所以会使用A接口的hello方法。
当然C++的菱形问题比这个更复杂,C中访问的是B,D的对象副本,因为类中还有很多成员变量,不同的方法可能还会影响不同对象的成员变量。因此如果要使用A中的方法,还需要显示声明这些方法来自B还是来自D,修改B的成员变量不会在D对象的副本中反映出来。
欢迎关注我的公众号