默認(rèn)方法的由來
我們都知道在java8新特性中對(duì)于接口也加入了一個(gè)新的改動(dòng)花墩,那就是默認(rèn)方法了,那為什么要加入這個(gè)新的變動(dòng)呢澄步?這是因?yàn)樵趈ava8的設(shè)計(jì)中冰蘑,加入了Lambda表達(dá)式和函數(shù)式接口,包括stream村缸、parallelStream等這些集合類的方法祠肥,這個(gè)在java8之前都是不存在的,它實(shí)現(xiàn)的Collection<T>接口也沒有梯皿,這是因?yàn)橐婚_始的設(shè)計(jì)也沒考慮這些方法仇箱。如果要重寫的,那改動(dòng)將會(huì)非常大:因?yàn)榻o接口加入一個(gè)新的實(shí)現(xiàn)方法东羹,那么所有實(shí)現(xiàn)他的類都要提供一個(gè)實(shí)現(xiàn)剂桥,這很難控制。
為了規(guī)避上面那么改動(dòng)帶來的問題属提,于是接口提供了一個(gè)默認(rèn)方法权逗,由他自己來進(jìn)行默認(rèn)實(shí)現(xiàn),而不是由實(shí)現(xiàn)類來提供垒拢。這樣做的好處式給設(shè)計(jì)者提供了一個(gè)擴(kuò)充接口的方式旬迹,而不會(huì)破壞現(xiàn)有的代碼。在java8中實(shí)現(xiàn)求类,使用default關(guān)鍵字來表示默認(rèn)方法奔垦。
默認(rèn)方法是什么
默認(rèn)方法就是接口里面可以有實(shí)現(xiàn)的方法,并且不需要實(shí)現(xiàn)類去實(shí)現(xiàn)其方法尸疆。并且默認(rèn)方法允許你添加新的方法到現(xiàn)在的接口中椿猎,并且確保與舊版本的兼容性。實(shí)現(xiàn)方式就是在接口簽名前面加入default關(guān)鍵字的方法就是默認(rèn)方法了寿弱。
實(shí)現(xiàn)類可以自動(dòng)繼承接口的默認(rèn)方法犯眠,并且不用實(shí)現(xiàn)默認(rèn)方法,就可以直接調(diào)用症革。同時(shí)默認(rèn)方法也可以重寫筐咧。
實(shí)現(xiàn)實(shí)例代碼
實(shí)例一
// 接口
public interface Test {
default void sout() {
System.out.println("調(diào)用Test里面的默認(rèn)方法!");
}
}
// 實(shí)現(xiàn)類自動(dòng)繼承接口的默認(rèn)方法,并且不用實(shí)現(xiàn)默認(rèn)方法量蕊,就可以直接調(diào)用
public class TestDefaultMethod{
public static void main(String[] args) {
test1 test1 = new test1();
test1.sout();
}
}
class test1 implements Test{
}
示例二: 可以重寫
class test1 implements Test{
@Override
public void sout() {
System.out.println("默認(rèn)方法也可以繼承铺罢!");
}
}
示例三實(shí)現(xiàn)多個(gè)接口的時(shí)候,且有相同方法的調(diào)用問題的解決
public class TestDefaultMethod {
public static void main(String[] args) {
IntTest test = new test();
test.sout();
}
}
interface IntTest {
default void sout() {
System.out.println("調(diào)用Test里面的默認(rèn)方法残炮!");
}
// 接口可以聲明靜態(tài)方法 并且有實(shí)現(xiàn)類韭赘。
static void syso() {
System.out.println("調(diào)用Test里面的靜態(tài)方法!");
}
}
interface IntTest2 {
default void sout() {
System.out.println("調(diào)用Test2里面的默認(rèn)方法势就!");
}
}
class test implements IntTest, IntTest2 {
public void sout() {
IntTest2.super.sout();
IntTest.super.sout();
IntTest.syso();
System.out.println("本方法里面的輸出泉瞻!");
}
}
可以看一下,這里面有兩個(gè)接口苞冯,并且兩個(gè)接口中都有sout方法袖牙,然后test把兩個(gè)都實(shí)現(xiàn)了,但是如果只調(diào)用一個(gè)方法抱完,會(huì)報(bào)錯(cuò)贼陶,所以想要調(diào)用接口里面的默認(rèn)方法就可以用接口名.super.方法名。用這個(gè)方法不會(huì)出現(xiàn)沖突巧娱。
并且上面還有一個(gè)新特性,接口可以聲明靜態(tài)方法 并且有實(shí)現(xiàn)類烘贴。
默認(rèn)方法的繼承
和其他方法一樣禁添,默認(rèn)方法也可以被繼承的,接口默認(rèn)方法的繼承分三種情況(分別對(duì)應(yīng)下面的 InterfaceB 接口刊棕、InterfaceC 接口和 InterfaceD 接口):
不覆寫默認(rèn)方法篙梢,直接從父接口中獲取方法的默認(rèn)實(shí)現(xiàn)卢鹦。
覆寫默認(rèn)方法,這跟類與類之間的覆寫規(guī)則相類似铺峭。
覆寫默認(rèn)方法并將它重新聲明為抽象方法,這樣新接口的子類必須再次覆寫并實(shí)現(xiàn)這個(gè)抽象方法汽纠。
interface InterfaceA {
default void foo() {
System.out.println("InterfaceA foo");
}
}
interface InterfaceB extends InterfaceA {
}
interface InterfaceC extends InterfaceA {
@Override
default void foo() {
System.out.println("InterfaceC foo");
}
}
interface InterfaceD extends InterfaceA {
@Override
void foo();
}
public class Test {
public static void main(String[] args) {
new InterfaceB() {}.foo(); // 打游兰:“InterfaceA foo”
new InterfaceC() {}.foo(); // 打印:“InterfaceC foo”
new InterfaceD() {
@Override
public void foo() {
System.out.println("InterfaceD foo");
}
}.foo(); // 打邮洹:“InterfaceD foo”
// 或者使用 lambda 表達(dá)式
((InterfaceD) () -> System.out.println("InterfaceD foo")).foo();
}
}
接口繼承行為發(fā)生沖突時(shí)的解決規(guī)則
Java 使用的是單繼承莉炉、多實(shí)現(xiàn)的機(jī)制,為的是避免多繼承帶來的調(diào)用歧義的問題碴犬。當(dāng)接口的子類同時(shí)擁有具有相同簽名的方法時(shí)絮宁,就需要考慮一種解決沖突的方案。
如下所示:
interface InterfaceA {
default void foo() {
System.out.println("InterfaceA foo");
}
}
interface InterfaceB {
default void bar() {
System.out.println("InterfaceB bar");
}
}
interface InterfaceC {
default void foo() {
System.out.println("InterfaceC foo");
}
default void bar() {
System.out.println("InterfaceC bar");
}
}
class ClassA implements InterfaceA, InterfaceB {
}
// 錯(cuò)誤
//class ClassB implements InterfaceB, InterfaceC {
//}
// 正確的解決方式
/**
在 ClassB 類中服协,它實(shí)現(xiàn)的 InterfaceB 接口和 InterfaceC 接口中都存在相同簽名的 foo 方法绍昂,需要手動(dòng)解決沖突。覆寫存在歧義的方法,并可以使用 InterfaceName.super.methodName(); 的方式手動(dòng)調(diào)用需要的接口默認(rèn)方法窘游。
*/
class ClassB implements InterfaceB, InterfaceC {
@Override
public void bar() {
InterfaceB.super.bar(); // 調(diào)用 InterfaceB 的 bar 方法
InterfaceC.super.bar(); // 調(diào)用 InterfaceC 的 bar 方法
System.out.println("ClassB bar"); // 做其他的事
}
}
還有一種沖突注意情況唠椭,看下面代碼:
interface InterfaceA {
default void foo() {
System.out.println("InterfaceA foo");
}
}
interface InterfaceB extends InterfaceA {
@Override
default void foo() {
System.out.println("InterfaceB foo");
}
}
// 正確
class ClassA implements InterfaceA, InterfaceB {
}
class ClassB implements InterfaceA, InterfaceB {
@Override
public void foo() {
// InterfaceA.super.foo(); // 錯(cuò)誤
InterfaceB.super.foo();
}
}
當(dāng) ClassB 類覆寫 foo 方法時(shí),無法通過 InterfaceA.super.foo(); 調(diào)用 InterfaceA 接口的 foo 方法张峰。因?yàn)?InterfaceB 接口繼承了 InterfaceA 接口泪蔫,那么 InterfaceB 接口一定包含了所有 InterfaceA 接口中的字段方法,因此一個(gè)同時(shí)實(shí)現(xiàn)了 InterfaceA 接口和 InterfaceB 接口的類與一個(gè)只實(shí)現(xiàn)了 InterfaceB 接口的類完全等價(jià)喘批。
在上面的 ClassA 類中不會(huì)出現(xiàn)方法名歧義的原因是所謂“存在歧義”的方法其實(shí)都來自于 InterfaceA 接口撩荣,InterfaceB 接口中的“同名方法”只是繼承自 InterfaceA 接口而來并對(duì)其進(jìn)行了覆寫。ClassA 類實(shí)現(xiàn)的兩個(gè)接口不是兩個(gè)毫不相干的接口饶深,因此不存在同名歧義方法餐曹。
而覆寫意味著對(duì)父類方法的屏蔽,這也是 Override 的設(shè)計(jì)意圖之一敌厘。因此在實(shí)現(xiàn)了 InterfaceB 接口的類中無法訪問已被覆寫的 InterfaceA 接口中的 foo 方法台猴。
這是當(dāng)接口繼承行為發(fā)生沖突時(shí)的規(guī)則之一,即 被其它類型所覆蓋的方法會(huì)被忽略俱两。
如果想要調(diào)用 InterfaceA 接口中的 foo 方法饱狂,只能通過自定義一個(gè)新的接口同樣繼承 InterfaceA 接口并顯示地覆寫 foo方法,在方法中使用 InterfaceA.super.foo(); 調(diào)用 InterfaceA 接口的 foo 方法宪彩,最后讓實(shí)現(xiàn)類同時(shí)實(shí)現(xiàn) InterfaceB接口和自定義的新接口休讳,代碼如下:
interface InterfaceA {
default void foo() {
System.out.println("InterfaceA foo");
}
}
interface InterfaceB extends InterfaceA {
@Override
default void foo() {
System.out.println("InterfaceB foo");
}
}
interface InterfaceC extends InterfaceA {
@Override
default void foo() {
InterfaceA.super.foo();
}
}
class ClassA implements InterfaceB, InterfaceC {
@Override
public void foo() {
InterfaceB.super.foo();
InterfaceC.super.foo();
}
}
注意! 雖然 InterfaceC 接口的 foo 方法只是調(diào)用了一下父接口的默認(rèn)實(shí)現(xiàn)方法尿孔,但是這個(gè)覆寫 不能省略俊柔,否則 InterfaceC 接口中繼承自 InterfaceA 接口的隱式的 foo 方法同樣會(huì)被認(rèn)為是被 InterfaceB 接口覆寫了而被屏蔽,會(huì)導(dǎo)致調(diào)用 InterfaceC.super.foo() 時(shí)出錯(cuò)活合。
通過這個(gè)例子雏婶,應(yīng)該注意到在使用一個(gè)默認(rèn)方法前,一定要考慮它是否真的需要白指。因?yàn)?默認(rèn)方法會(huì)帶給程序歧義留晚,并且在復(fù)雜的繼承體系中容易產(chǎn)生編譯錯(cuò)誤。濫用默認(rèn)方法可能給代碼帶來意想不到侵续、莫名其妙的錯(cuò)誤倔丈。
接口與抽象類
接口繼承行為發(fā)生沖突時(shí)的另一個(gè)規(guī)則是,類的方法聲明優(yōu)先于接口默認(rèn)方法状蜗,無論該方法是具體的還是抽象的需五。
其他注意點(diǎn)
default 關(guān)鍵字只能在接口中使用(以及用在 switch 語句的 default 分支),不能用在抽象類中轧坎。
接口默認(rèn)方法不能覆寫 Object 類的 equals宏邮、hashCode 和 toString 方法。
接口中的靜態(tài)方法必須是 public 的,public 修飾符可以省略蜜氨,static 修飾符不能省略械筛。
即使使用了 java 8 的環(huán)境,一些 IDE 仍然可能在一些代碼的實(shí)時(shí)編譯提示時(shí)出現(xiàn)異常的提示(例如無法發(fā)現(xiàn) java 8 的語法錯(cuò)誤)飒炎,因此不要過度依賴 IDE埋哟。
[]https://www.cnblogs.com/sidesky/p/9287710.html
https://www.cnblogs.com/xdtg/p/11982644.html
《java 8實(shí)戰(zhàn)