導航
default
default是Java8新增的一個關鍵字,該關鍵字用於在接口中聲明方法,使用default聲明的方法稱爲默認方法。
默認方法
在Java8之前接口中聲明的方法全部都是抽象方法,不可以有具體實現,實現接口的類需要重寫接口中聲明的每個方法。但是從Java8開始接口中聲明的方法可以有具體實現,更準確的說是接口中的默認方法可以有具體實現。不僅如此,從Java8開始接口中可以定義靜態方法。
那麼爲什麼會出現默認方法呢?其主要原因是Java8中引入了Stream。
對集合而言,每種集合都需要有獲取Stream對象的方法——stream()。按照設計理念stream()方法應該在集合根接口Collection中聲明,然後讓Collection的實現類重寫該方法。然而集合體系很龐大,如果直接在Collection接口中聲明stream()方法,那麼所有的Collection實現類都要重寫該方法,對現有的架構改動極大。
所以如果Collection接口中可以定義有具體實現的stream()方法就不會破壞現有的架構,Collection的實現類只需要直接繼承過來就可以了。在JDK8的源碼中,我們可以看到Collection接口中定義有默認方法stream():
public interface Collection<E> extends Iterable<E> {
default Stream<E> stream() { ... }
...
}
下面演示默認方法的使用:
public class TestDefault {
public static void main(String[] ar) {
// 用Lambda表達式表示函數式接口的一個具體實現
MyInterface myInterface = () -> 1;
MyInterface.getInfo(myInterface.size(), myInterface.isEmpty());
}
}
// 函數式接口
interface MyInterface {
// 抽象方法
int size();
// 默認犯法
default boolean isEmpty () {
return size() == 0;
}
// 靜態方法
static void getInfo(int size, boolean isEmpty) {
System.out.println("size is " + size + ", empty: " + isEmpty);
}
}
打印結果如下:
size is 1, empty: false
MyInterface接口中存在三個方法:一個抽象方法size(),一個靜態方法getInfo()以及一個默認方法isEmpty()。因此MyInterface是一個函數式接口,我們可以用Lambda表達式可以表示函數式接口的一個具體實現——myInterface。
myInterface從MyInterface接口繼承了有具體實現的isEmpty()方法,可以不用重寫isEmpty()方法就直接調用該方法。
默認方法與衝突
在Java中有一條亙久不變的定理:類只能單繼承,接口可以多實現。
Java8之前由於接口中的方法全部都是抽象方法,一個類如果同時實現兩個擁有相同方法簽名的接口並不會出現衝突,實現類只需要重寫該方法即可。但是在Java8中出現了默認方法,這樣一來就有可能會出現Java一直在避免的多繼承問題——一個類從多個地方(類或接口)繼承了有相同方法簽名的方法。
遇到這種情況時,我們需要遵循下面三條原則解決衝突:
- 一、類中方法優先級最高。類或父類中定義方法的優先級高於任何聲明爲默認方法的優先級。
- 二、第一條原則無法進行判斷,子接口中聲明的默認方法的優先級僅次於類中聲明方法的優先級。
- 三、上述兩條原則仍然無法判斷,實現類必須顯式重寫方法或選擇使用哪一個默認方法的實現。
類與接口方法衝突
public class TestDefault1 {
public static void main(String[] ar) {
new Class1().print();;
}
}
class ClassFu {
public void print() {
System.out.println("It's ClassFu.");
}
}
interface A1 {
default void print () {
System.out.println("It's A1.");
}
}
interface B1 {
default void print() {
System.out.println("It's B1.");
}
}
class Class1 extends ClassFu implements A1,B1{ }
打印結果如下:
It's ClassFu.
例子中Class1繼承了ClassFu並實現了接口A1和B1,A1和B1兩個接口都聲明瞭默認方法print(),ClassFu類中也定義有print()方法。那麼調用Class1對象的print()方法時應當按照第一條原則:類或父類中聲明方法的優先級最高,打印結果符合預期。
父接口與子接口方法衝突
public class TestDefault2 {
public static void main(String[] ar) {
new Class2().print();
}
}
interface A2 {
default void print () {
System.out.println("It's A2.");
}
}
interface B2 extends A2{
@Override
default void print() {
System.out.println("It's B2.");
}
}
class Class2 implements B2,A2 {}
打印結果如下:
It's B2.
例子中Class2同時實現了接口A2和B2,且接口B2繼承了A2並重寫了A2中的默認方法print()。那麼調用Class2對象的print()方法時應該按照第二條原則:子接口的默認方法優先級更高,打印結果符合我們的預期。
接口與接口方法衝突
public class TestDefault3 {
public static void main(String[] ar) {
new Class3().print();
}
}
interface A3 {
default void print () {
System.out.println("It's A3.");
}
}
interface B3 {
default void print() {
System.out.println("It's B3.");
}
}
class Class3 implements A3,B3 {
@Override
public void print() {
A3.super.print();
}
}
打印結果如下:
It's A3.
例子中Class3同時實現了接口A3和B3,A3和B3接口聲明瞭有相同方法簽名的默認方法print(),此時按照第三條原則:必須在類中重寫默認方法。這裏我們選擇A3接口中的默認方法作爲方法實現,調用Class3對象的print()方法的打印結果符合預期。