Java8新特性(五) default與默認方法

導航

default

默認方法

默認方法與衝突

類與接口方法衝突

父接口與子接口方法衝突

接口與接口方法衝突


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()方法的打印結果符合預期。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章