默认方法
默认方法由default
修饰符修饰,并像类中声明的其他方法一样包含方法体。
比如,你可以像下面这样在集合库中定义一个名为
Sized
的接口,在其中定义一个抽象方法size
,以及一个默认方法isEmpty
:
public interface Sized {
int size();
default boolean isEmpty() {
return size() == 0;
}
}
这样任何一个实现了Sized接口的类都会自动继承isEmpty默认方法的实现。
为什么要有这个特性?
首先,之前的接口是个双刃剑,好处是面向抽象而不是面向具体编程,缺陷是,当需要修改接口时候,需要修改全部实现该接口的类,目前的java8之前的集合框架没有foreach
方法,通常能想到的解决办法是在JDK里给相关的接口添加新的方法及实现。然而,对于已经发布的版本,是没法在给接口添加新方法的同时不影响已有的实现。所以引进的默认方法。他们的目的是为了解决接口的修改与现有的实现不兼容的问题。
那么抽象类和抽象接口之间的区别是什么呢?
首先,一个类只能继承一个抽象类,但是一个类可以实现多个接口。
其次,一个抽象类可以通过实例变量(字段)保存一个通用状态,而接口是不能有实例变量的。
使用默认方法的两种用例
可选方法
你很可能也碰到过这种情况,类实现了接口,不过却刻意地将一些方法的实现留白。
我们以Iterator接口为例来说。Iterator接口定义了hasNext、next,还定义了remove方法。
Java 8之前,由于用户通常不会使用该方法,remove方法常被忽略。因此,实现Iterator接口的类通常会为remove方法放置一个空的实现,这些都是些毫无用处的模板代码。
采用默认方法之后,你可以为这种类型的方法提供一个默认的实现,这样实体类就无需在自己的实现中显式地提供一个空方法。
比如,在Java 8中,Iterator接口就为remove方法提供了
一个默认实现,如下所示:
interface Iterator<T> {
boolean hasNext();
T next();
default void remove() {
throw new UnsupportedOperationException();
}
}
通过这种方式,你可以减少无效的模板代码。实现Iterator接口的每一个类都不需要再声明一个空的remove方法了,因为它现在已经有一个默认的实现。
行为的多继承
这是一种让类从多个来源重用代码的能力。
如下所示代码:
A.java
public interface A {
void doSomething();
default void hello() {
System.out.println("hello world from interface A");
}
default void foo() {
System.out.println("foo from interface A");
}
}
B.java
interface B extends A {
default void hello() {
System.out.println("hello world from interface B");
}
}
C.java
public class C implements A, B{
@Override
public void doSomething() {
System.out.println("c object need do something");
}
public static void main(String[] args) {
A obj = new C();
obj.hello(); //调用B的默认方法
obj.foo(); //调用A的默认方法
obj.doSomething();
}
}
输出:
hello world from interface B
foo from interface A
c object need do something
obj.hello()调用的是B接口中的默认方法而不是A接口的默认方法。
为什么会这样呢?这就引出默认方法中解决冲突的原则。
如果一个类使用相同的函数签名从多个地方(比如另一个类或接口)继承了方法,通过三条规则可以进行判断:
1.类中的方法优先级最高,类或父类中声明的方法的优先级高于任何声明为默认方法的优先级。
2.如果无法依据第一条进行判断,那么子接口的优先级更高:函数签名相同时,优先选择拥有最具体实现的默认方法的接口,即如果B继承了A,那么B就比A更加具体。
3.最后,如果还是无法判断,继承了多个接口的类必须通过显示覆盖和调用期望的方法,显示地选择使用哪一个默认方法的实现。
再来看一个栗子:
public interface A {
void hello() {
System.out.println("Hello from A");
}
}
public interface B {
void hello() {
System.out.println("Hello from B");
}
}
public class C implements B, A { }
这时规则(2)就无法进行判断了,因为从编译器的角度看没有哪一个接口的实现更加具体,两个都差不多。
A接口和B接口的hello方法都是有效的选项。所以,Java编译器这时就会抛出一个编译错误,因为它无法判断哪一个方法更合适:“Error: class C inherits unrelated defaults for hello()
from types B and A.”
如果你希望C使用来自于B的默认方法,它的调用方式看起来就如下所示
public class C implements B, A {
void hello(){
B.super.hello();
}
}
显式地选择调用接口B中的方法 。
另外也可以在接口中声明静态方法:
static void statichello() {
System.out.println("statichello world from interface B");
}
Java 8中的接口可以通过默认方法和静态方法提供方法的代码实现。