JAVA8新特性之默認(rèn)方法

默認(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)

  1. default 關(guān)鍵字只能在接口中使用(以及用在 switch 語句的 default 分支),不能用在抽象類中轧坎。

  2. 接口默認(rèn)方法不能覆寫 Object 類的 equals宏邮、hashCode 和 toString 方法。

  3. 接口中的靜態(tài)方法必須是 public 的,public 修飾符可以省略蜜氨,static 修飾符不能省略械筛。

  4. 即使使用了 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)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市郎汪,隨后出現(xiàn)的幾起案子赤赊,更是在濱河造成了極大的恐慌,老刑警劉巖煞赢,帶你破解...
    沈念sama閱讀 216,470評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件抛计,死亡現(xiàn)場離奇詭異,居然都是意外死亡照筑,警方通過查閱死者的電腦和手機(jī)吹截,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來凝危,“玉大人波俄,你說我怎么就攤上這事《昴” “怎么了弟断?”我有些...
    開封第一講書人閱讀 162,577評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長趴生。 經(jīng)常有香客問我,道長昏翰,這世上最難降的妖魔是什么苍匆? 我笑而不...
    開封第一講書人閱讀 58,176評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮棚菊,結(jié)果婚禮上浸踩,老公的妹妹穿的比我還像新娘。我一直安慰自己统求,他們只是感情好检碗,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,189評(píng)論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著码邻,像睡著了一般折剃。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上像屋,一...
    開封第一講書人閱讀 51,155評(píng)論 1 299
  • 那天怕犁,我揣著相機(jī)與錄音,去河邊找鬼。 笑死奏甫,一個(gè)胖子當(dāng)著我的面吹牛戈轿,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播阵子,決...
    沈念sama閱讀 40,041評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼思杯,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了挠进?” 一聲冷哼從身側(cè)響起色乾,我...
    開封第一講書人閱讀 38,903評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎奈梳,沒想到半個(gè)月后杈湾,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,319評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡攘须,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,539評(píng)論 2 332
  • 正文 我和宋清朗相戀三年漆撞,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片于宙。...
    茶點(diǎn)故事閱讀 39,703評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡浮驳,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出捞魁,到底是詐尸還是另有隱情至会,我是刑警寧澤,帶...
    沈念sama閱讀 35,417評(píng)論 5 343
  • 正文 年R本政府宣布谱俭,位于F島的核電站奉件,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏昆著。R本人自食惡果不足惜县貌,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,013評(píng)論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望凑懂。 院中可真熱鬧煤痕,春花似錦、人聲如沸接谨。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,664評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽脓豪。三九已至巷帝,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間跑揉,已是汗流浹背锅睛。 一陣腳步聲響...
    開封第一講書人閱讀 32,818評(píng)論 1 269
  • 我被黑心中介騙來泰國打工埠巨, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人现拒。 一個(gè)月前我還...
    沈念sama閱讀 47,711評(píng)論 2 368
  • 正文 我出身青樓辣垒,卻偏偏與公主長得像,于是被迫代替她去往敵國和親印蔬。 傳聞我的和親對(duì)象是個(gè)殘疾皇子勋桶,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,601評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容