Java 學(xué)習(xí)筆記(7)——接口與多態(tài)

上一篇說了Java面向?qū)ο笾械睦^承關(guān)系笤虫,在繼承中說到:調(diào)用對象中的成員變量時壳贪,根據(jù)引用類型來決定調(diào)用誰,而調(diào)用成員方法時由于多態(tài)的存在哎榴,具體調(diào)用誰的方法需要根據(jù)new出來的對象決定型豁,這篇主要描述的是Java中的多態(tài)以及利用多態(tài)形成的接口

多態(tài)

當(dāng)時在學(xué)習(xí)C++時,要使用多態(tài)需要定義函數(shù)為virtual尚蝌,也就是虛函數(shù)偷遗。類中存在虛函數(shù)時,對象會有一個虛函數(shù)表的頭指針驼壶,虛函數(shù)表會存儲虛函數(shù)的地址氏豌,在使用父類的指針或者引用來調(diào)用方法時會根據(jù)虛函數(shù)表中的函數(shù)地址來調(diào)用函數(shù),會形成多態(tài)热凹。

當(dāng)時學(xué)習(xí)C++時對多態(tài)有一個非常精煉的定義:基類的指針指向不同的派生類泵喘,其行為不同。這里行為不同指的是調(diào)用同一個虛函數(shù)時般妙,會調(diào)用不同的派生類函數(shù)纪铺。這里我們說形成多態(tài)的幾個基本條件:1)指針或者引用類型是基類;2)需要指向派生類碟渺;3)調(diào)用的函數(shù)必須是基類重寫的函數(shù)鲜锚。

public class Parent{
    public void sayHelllo(){
        System.out.println("Hello Parent");
    }

    public void sayHello(String name){
        System.out.println("Hello" + name);
    }
}

public class Child extends Parent{
    public void sayHello(){
        System.out.println("Hello Child");
    }
}

根據(jù)上述的繼承關(guān)系,我們來看下面幾個實例代碼苫拍,分析一下哪些是多態(tài)

Parent obj = new  Child();
obj.sayHello();

該實例構(gòu)成了多態(tài)芜繁,它滿足了多態(tài)的三個條件:Parent 類型的 obj 引用指向了 new出來的Child子類、并且調(diào)用了二者共有的方法绒极。

Parent obj = new  Child();
obj.sayHello("Tom");

這個例子沒有構(gòu)成多態(tài)骏令,雖然它滿足基類的引用指向派生類,但是它調(diào)用了父類特有的方法垄提。

Parent obj = new  Parent();
obj.sayHello();

這個例子也不滿足多態(tài)榔袋,它使用父類的引用指向了父類,這里就是一個正常的類方法調(diào)用,它會調(diào)用父類的方法

Child obj = new Child();
obj.sayHello();

這個例子也不滿足多態(tài)铡俐,它使用子類的引用指向了子類凰兑,這里就是一個正常的類方法調(diào)用,它會調(diào)用子類的方法

那么多態(tài)有什么好處呢?引入多態(tài)實質(zhì)上也是為了避免重復(fù)的代碼审丘,而且程序更具有擴展性吏够,我們通過println函數(shù)來說明這個問題。

public void println(Object x) {
    String s = String.valueOf(x);
    synchronized (this) {
        print(s);
        newLine();
    }
}

//Class String
public static String valueOf(Object obj) {
    return (obj == null) ? "null" : obj.toString();
}

函數(shù)println實現(xiàn)了一個傳入Object的重載,該函數(shù)調(diào)用了String類的靜態(tài)方法 valueOf, 進一步跟到String類中發(fā)現(xiàn)稿饰,該方法只是調(diào)用了類的 toString 方法锦秒,傳入的obj可以是任意繼承Object的類(在Java中只要是對象就一定是繼承自O(shè)bject),只要類重寫了 toString 方法就可以直接打印。這樣一個函數(shù)就實現(xiàn)了重用喉镰,相比于需要后來的人額外重載println函數(shù)來說旅择,要方便很多。

類類型轉(zhuǎn)化

上面的println 函數(shù)侣姆,它需要傳入的是Object類的引用生真,但是在調(diào)用該方法時,從來都沒有進行過類型轉(zhuǎn)化捺宗,都是直接傳的柱蟀,這里是需要進行類型轉(zhuǎn)化的,在由子類轉(zhuǎn)到父類的時候蚜厉,Java進行了隱式類型轉(zhuǎn)化长已。大轉(zhuǎn)小一定是安全的(這里的大轉(zhuǎn)小是對象的內(nèi)存包含關(guān)系),子類一定可以包含父類的成員昼牛,所以即使轉(zhuǎn)化為父類也不存在問題术瓮。而父類引用指向的內(nèi)存不一定就是包含了子類成員,所以小轉(zhuǎn)大不安全贰健。

為什么要進行小轉(zhuǎn)大呢胞四?雖然多態(tài)給了我們很大的方便,但是多態(tài)最大的問題就是父類引用無法看到子類的成員伶椿,也就是無法使用子類中的成員辜伟。這個時候如果要使用子類的成員就必須進行小轉(zhuǎn)大的操作。之前說過小轉(zhuǎn)大不安全脊另,由于父類可能有多個實現(xiàn)類导狡,我們無法確定傳進來的參數(shù)就是我們需要的子類的對象,所以java引入了一個關(guān)鍵字 instanceof 來判斷是否可以進行安全的轉(zhuǎn)化尝蠕,只要傳進來的對象引用是目標(biāo)類的對象或者父類對象它就會返回true烘豌,比如下面的例子

Object obj = "hello"
System.out.println(obj instanceof String); //true
System.out.println(obj instanceof Object); //true
System.out.println(obj instanceof StringBuffer); //false
System.out.println(obj instanceof CharSequence); //true

抽象方法和抽象類

我們說有了多態(tài)可以使代碼重用性更高。但是某些時候我們針對幾個有共性的類看彼,抽象出了更高層面的基類,但是發(fā)現(xiàn)基類雖然有一些共性的內(nèi)容囚聚,但是有些共有的方法不知道如何實現(xiàn)靖榕,比如說教科書上經(jīng)常舉例的動物類,由于不知道具體的動物是什么顽铸,所以也無法判斷該動物是食草還是食肉茁计。所以一般將動物的 eat 定義為抽象方法,擁有抽象方法的類一定必須是抽象基類。

抽象方法是不需要寫實現(xiàn)的方法星压,它只需提供一個函數(shù)的原型践剂。而抽象類不能創(chuàng)建實例,必須有派生類重寫抽象方法娜膘。為什么抽象類不能創(chuàng)建對象呢逊脯?對象調(diào)用方法本質(zhì)上是根據(jù)函數(shù)表找到函數(shù)對應(yīng)代碼所在的內(nèi)存地址,而抽象方法是未實現(xiàn)的方法竣贪,自然就無法給出方法的地址了军洼,如果創(chuàng)建了對象,而我的對象又想調(diào)用這個抽象方法那不就沖突了嗎演怎。所以規(guī)定無法實例化抽象類匕争。

抽象方法的定義使用關(guān)鍵字 abstract,例如

public abstract class Life{
    public abstract void happy();
}

public class Cat{
    public void happy(){
        System.out.println("貓吃魚");
    }
}

public class Cat{
    public void happy(){
        System.out.println("狗吃肉");
    }
}

public class Altman{
    public void happy(){
        System.out.println("奧特曼打小怪獸");
    }
}

上面定義了一個抽象類Life 代表世間的生物爷耀,你要問生物的幸福是什么甘桑,可能沒有人給你答案,不同的生物有不同的回答歹叮,但是具體到同一種生物扇住,可能就有答案了,這里簡單的給出了答案:幸福就是貓吃魚狗吃肉奧特曼愛打小怪獸盗胀。

使用抽象類需要注意下面幾點:

  • 不能直接創(chuàng)建抽象類的對象艘蹋,必須使用實現(xiàn)類來創(chuàng)建對象
  • 實現(xiàn)類必須實現(xiàn)抽象類的所有抽象方法,否則該實現(xiàn)類也必須是抽象類
  • 抽象類可以有自己的構(gòu)造方法票灰,該方法僅供子類構(gòu)造時使用
  • 抽象類可以沒有抽象方法女阀,但是有抽象方法的一定要是抽象類

接口

接口就是一套公共的規(guī)范標(biāo)準(zhǔn),只要符合標(biāo)準(zhǔn)就能通用屑迂,比如說USB接口浸策,只要一個設(shè)備使用了USB接口,那么我的電腦不管你的設(shè)備是什么惹盼,插上就應(yīng)該能用庸汗。在代碼中接口就是多個類的公共規(guī)范。

Java中接口也是一個引用類型手报。接口與抽象類非常相似蚯舱,同樣不能創(chuàng)建對象,必須創(chuàng)建實現(xiàn)類的方法掩蛤。但是接口與抽象類還是有一些不同的枉昏。 抽象類也是一個類,它是從底層類中抽象出來的更高層級的類揍鸟,但是接口一般用來聯(lián)系多個類兄裂,是多個類需要實現(xiàn)的一個共同的標(biāo)準(zhǔn)。是從頂層一層層擴展出來的。

接口的一個常見的使用場景就是回調(diào)晰奖,比如說常見的窗口消息處理函數(shù)谈撒。這個場景C++中一般使用函數(shù)指針,而Java中主要使用接口匾南。
接口使用關(guān)鍵字 interface 來定義, 比如

public interface USB{
    public final String deviceType = "USB"; 
    public abstract void open();
    public abstract void close();
}

接口中常見的一個成員是抽象方法啃匿,抽象方法也是由實現(xiàn)類來實現(xiàn),注意事項也與之前的抽象類相同午衰。除了有抽象方法立宜,接口中也可以有常量。

接口中的抽象方法是沒有方法體的臊岸,它需要實現(xiàn)類來實現(xiàn)橙数,所以實現(xiàn)類與接口中發(fā)生重寫現(xiàn)象時會調(diào)用實現(xiàn)類,那么常量呢?

public class Mouse implements USB{
    public final String deviceType = "鼠標(biāo)";
    public void open(){

    }

    public void close(){

    }
}

public class Demo{
    public static void main(String[] args){
        USB usb = new Mouse();
        System.out.println(usb.deviceType);
    }
}

常量的調(diào)用遵循之前說的重載中的屬性成員調(diào)用的方式帅戒。使用的是什么類型的引用灯帮,調(diào)用哪個類型中的成員。

與抽象類中另一個重要的不同是逻住,接口運行多繼承钟哥,那么在接口的多繼承中是否會出現(xiàn)沖突的問題呢

public interface Storage{
    public final String deviceType = "存儲設(shè)備";
    public abstract void write();
    public abstract void read();
}

public class MobileHardDisk implements USB, Storage{
     public void open(){

    }

    public void close(){

    }

    public void write(){

    }

    public void read(){

    }
}

public class Demo{
    public static void main(String[] args){
        MobileHardDisk mhd = new MobileHardDisk();
        System.out.println(mhd.deviceType);
    }
}

編譯上述代碼時會發(fā)現(xiàn)報錯了,提示 USB 中的變量 deviceType 和 Storage 中的變量 deviceType 都匹配 瞎访,也就是說Java中仍然沒有完全避免沖突問題腻贰。

接口中的默認方法

有的時候可能會出現(xiàn)這樣的情景,當(dāng)項目完成后扒秸,可能客戶需求有變播演,導(dǎo)致接口中可能會添加一個方法,如果使用抽象方法伴奥,那么接口所有的實現(xiàn)類都得重復(fù)實現(xiàn)某個方法写烤,比如說上述的代碼中,USB接口需要添加一個方法通知PC設(shè)備我這是什么類型的USB設(shè)備拾徙,以便操作系統(tǒng)匹配對應(yīng)的驅(qū)動洲炊。那么可能USB的實現(xiàn)類都需要添加一個,這樣可能會引入大量重復(fù)代碼尼啡,針對這個問題暂衡,從Java 8開始引入了默認方法。

默認方法為了解決接口升級的問題玄叠,接口中新增默認方法時古徒,不用修改之前的實現(xiàn)類。

默認方法的使用如下:

public interface USB{
    public final String deviceType = "USB"; 
    public abstract void open();
    public abstract void close();
    public default String getType(){
        return this.deviceType;
    }
}

默認方法同樣可以被所有的實現(xiàn)類覆蓋重寫读恃。

接口中的靜態(tài)方法

從Java 8中開始,允許在接口中定義靜態(tài)方法,靜態(tài)方法可以使用實現(xiàn)類的對象進行調(diào)用寺惫,也可以使用接口名直接調(diào)用

接口中的私有方法

從Java 9開始運行在接口中定義私有方法疹吃,私有方法可以解決在默認方法中存在大量重復(fù)代碼的情況。

雖然Java為接口中新增了這么多屬性和擴展西雀,但是我認為不到萬不得已萨驶,不要隨便亂用這些東西,畢竟接口中應(yīng)該定義一系列需要實現(xiàn)的標(biāo)準(zhǔn)艇肴,而不是自己去實現(xiàn)這些標(biāo)準(zhǔn)腔呜。

最后總結(jié)一下使用接口的一些注意事項:

  • 接口沒有靜態(tài)代碼塊或者構(gòu)造方法
  • 一個類的父類只能是一個,但是類可以實現(xiàn)多個接口
  • 如果類實現(xiàn)的多個接口中有重名的默認方法再悼,那么實現(xiàn)類必須重寫這個實現(xiàn)方法核畴,不然會出現(xiàn)沖突。
  • 如果接口的實現(xiàn)類中沒有實現(xiàn)所有的抽象方法冲九,那么這個類必須是抽象類
  • 父類與接口中有重名的方法時谤草,優(yōu)先使用父類的方法,在Java中繼承關(guān)系優(yōu)于接口實現(xiàn)關(guān)系
  • 接口與接口之間是多繼承的莺奸,如果多個父接口中存在同名的默認方法丑孩,子接口中需要重寫默認方法,不然會出現(xiàn)沖突

final關(guān)鍵字

之前提到過final關(guān)鍵字灭贷,用來表示常量温学,也就是無法在程序中改變的量。除了這種用法外甚疟,它還有其他的用法

  • 修飾類仗岖,表示類不能有子類」潘可以將繼承關(guān)系理解為改變了這個類箩帚,既然final表示常量,不能修改黄痪,那么類自然也不能修改
  • 修飾方法:被final修飾的方法不能被重寫
  • 修飾成員變量:表示成員變量是常量紧帕,不能被修改
  • 修飾局部變量:表示局部變量是常量,在對應(yīng)作用域內(nèi)不可被修改

<hr />

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末桅打,一起剝皮案震驚了整個濱河市是嗜,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌挺尾,老刑警劉巖鹅搪,帶你破解...
    沈念sama閱讀 216,470評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異遭铺,居然都是意外死亡丽柿,警方通過查閱死者的電腦和手機恢准,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來甫题,“玉大人馁筐,你說我怎么就攤上這事∽狗牵” “怎么了敏沉?”我有些...
    開封第一講書人閱讀 162,577評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長炎码。 經(jīng)常有香客問我盟迟,道長,這世上最難降的妖魔是什么潦闲? 我笑而不...
    開封第一講書人閱讀 58,176評論 1 292
  • 正文 為了忘掉前任攒菠,我火速辦了婚禮,結(jié)果婚禮上矫钓,老公的妹妹穿的比我還像新娘要尔。我一直安慰自己,他們只是感情好新娜,可當(dāng)我...
    茶點故事閱讀 67,189評論 6 388
  • 文/花漫 我一把揭開白布赵辕。 她就那樣靜靜地躺著,像睡著了一般概龄。 火紅的嫁衣襯著肌膚如雪还惠。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,155評論 1 299
  • 那天私杜,我揣著相機與錄音蚕键,去河邊找鬼。 笑死衰粹,一個胖子當(dāng)著我的面吹牛锣光,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播铝耻,決...
    沈念sama閱讀 40,041評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼誊爹,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了瓢捉?” 一聲冷哼從身側(cè)響起频丘,我...
    開封第一講書人閱讀 38,903評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎泡态,沒想到半個月后搂漠,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,319評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡某弦,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,539評論 2 332
  • 正文 我和宋清朗相戀三年桐汤,在試婚紗的時候發(fā)現(xiàn)自己被綠了而克。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,703評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡惊科,死狀恐怖拍摇,靈堂內(nèi)的尸體忽然破棺而出亮钦,到底是詐尸還是另有隱情馆截,我是刑警寧澤,帶...
    沈念sama閱讀 35,417評論 5 343
  • 正文 年R本政府宣布蜂莉,位于F島的核電站蜡娶,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏映穗。R本人自食惡果不足惜窖张,卻給世界環(huán)境...
    茶點故事閱讀 41,013評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望蚁滋。 院中可真熱鬧宿接,春花似錦、人聲如沸辕录。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,664評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽走诞。三九已至副女,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間蚣旱,已是汗流浹背碑幅。 一陣腳步聲響...
    開封第一講書人閱讀 32,818評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留塞绿,地道東北人沟涨。 一個月前我還...
    沈念sama閱讀 47,711評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像异吻,于是被迫代替她去往敵國和親裹赴。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,601評論 2 353

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

  • 今日任務(wù): 1涧黄,能夠獨立使用抽象類 2篮昧,能夠獨立使用多態(tài) 3,能夠獨立使用接口 4笋妥,能夠理解適配器設(shè)計模式 1. ...
    Villain丶Cc閱讀 1,359評論 0 17
  • 面向?qū)ο笾饕槍γ嫦蜻^程懊昨。 面向過程的基本單元是函數(shù)。 什么是對象:EVERYTHING IS OBJECT(萬物...
    sinpi閱讀 1,054評論 0 4
  • 整理來自互聯(lián)網(wǎng) 1春宣,JDK:Java Development Kit酵颁,java的開發(fā)和運行環(huán)境嫉你,java的開發(fā)工具...
    Ncompass閱讀 1,537評論 0 6
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴謹 對...
    cosWriter閱讀 11,097評論 1 32
  • 一:java概述: 1,JDK:Java Development Kit躏惋,java的開發(fā)和運行環(huán)境幽污,java的開發(fā)...
    慕容小偉閱讀 1,788評論 0 10