Java多態(tài)總結(jié)

寫在前面

由于找工作的原因奋岁,最近幾個月都沒有更新博客了鸦采。篙骡。稽坤。這篇可能是今年最后一篇總結(jié)類的博文了,希望能夠?qū)懙暮命c吧 _
至此糯俗,博客中java基礎(chǔ)方面的內(nèi)容零零散散地也逐漸總結(jié)完了尿褪,基本上面試經(jīng)常問到的內(nèi)容都涵蓋了,希望大家喜歡

概念介紹

  • 定義
    多態(tài)得湘,是面向?qū)ο蟮某绦蛟O(shè)計語言最核心的特征杖玲。多態(tài),意味著一個對象有著多重特征淘正,可以在特定的情況下摆马,表現(xiàn)不同的狀態(tài)臼闻,從而對應(yīng)著不同的屬性和方法。

  • 實現(xiàn)技術(shù)
    動態(tài)綁定(dynamic binding)囤采,是指在執(zhí)行期間判斷所引用對象的實際類型述呐,根據(jù)其實際的類型調(diào)用其相應(yīng)的方法。

  • 作用
    消除類型之間的耦合關(guān)系蕉毯。

  • 應(yīng)用場景
    現(xiàn)實中乓搬,關(guān)于多態(tài)的例子不勝枚舉。比方說按下 F1 鍵這個動作代虾,如果當(dāng)前在 Flash 界面下彈出的就是 AS 3 的幫助文檔进肯;如果當(dāng)前在 Word 下彈出的就是 Word 幫助;在 Windows 下彈出的就是 Windows 幫助和支持棉磨。同一個事件發(fā)生在不同的對象上會產(chǎn)生不同的結(jié)果江掩。

深入理解多態(tài)

多態(tài)存在的三個必要條件:
一、要有繼承含蓉;
二频敛、要有重寫;
三馅扣、父類引用指向子類對象斟赚。

Java中多態(tài)的實現(xiàn)方式:
接口實現(xiàn)
繼承父類進(jìn)行方法重寫
同一個類中進(jìn)行方法重載

  • 方法表與方法調(diào)用

Java 的方法調(diào)用有兩類,動態(tài)方法調(diào)用與靜態(tài)方法調(diào)用差油。靜態(tài)方法調(diào)用是指對于類的靜態(tài)方法的調(diào)用方式拗军,是靜態(tài)綁定的;而動態(tài)方法調(diào)用需要有方法調(diào)用所作用的對象蓄喇,是動態(tài)綁定的发侵。類調(diào)用 (invokestatic) 是在編譯時刻就已經(jīng)確定好具體調(diào)用方法的情況,而實例調(diào)用 (invokevirtual) 則是在調(diào)用的時候才確定具體的調(diào)用方法妆偏,這就是動態(tài)綁定刃鳄,也是多態(tài)要解決的核心問題。

方法表是動態(tài)調(diào)用的核心钱骂,也是 Java 實現(xiàn)動態(tài)調(diào)用的主要方式叔锐。它被存儲于方法區(qū)中的類型信息,包含有該類型所定義的所有方法及指向這些方法代碼的指針见秽,注意這些具體的方法代碼可能是被覆寫的方法愉烙,也可能是繼承自基類的方法。

如有類定義 Person, Girl, Boy,

class Person { 
 public String toString(){ 
    return "I'm a person."; 
     } 
 public void eat(){} 
 public void speak(){} 
    
 } 

 class Boy extends Person{ 
 public String toString(){ 
    return "I'm a boy"; 
     } 
 public void speak(){} 
 public void fight(){} 
 } 

 class Girl extends Person{ 
 public String toString(){ 
    return "I'm a girl"; 
     } 
 public void speak(){} 
 public void sing(){} 
 }

當(dāng)這三個類被載入到 Java 虛擬機(jī)之后解取,方法區(qū)中就包含了各自的類的信息步责。Girl 和 Boy 在方法區(qū)中的方法表可表示如下:

Boy 和 Girl 的方法表

可以看到,Girl 和 Boy 的方法表包含繼承自 Object 的方法,繼承自直接父類 Person 的方法及各自新定義的方法蔓肯。注意方法表條目指向的具體的方法地址遂鹊,如 Girl 的繼承自 Object 的方法中,只有 toString() 指向自己的實現(xiàn)(Girl 的方法代碼)蔗包,其余皆指向 Object 的方法代碼稿辙;其繼承自于 Person 的方法 eat() 和 speak() 分別指向 Person 的方法實現(xiàn)和本身的實現(xiàn)。
Person 或 Object 的任意一個方法气忠,在它們的方法表和其子類 Girl 和 Boy 的方法表中的位置 (index) 是一樣的邻储。這樣 JVM 在調(diào)用實例方法其實只需要指定調(diào)用方法表中的第幾個方法即可。

如調(diào)用如下:

 class Party{ 
…
 void happyHour(){ 
 Person girl = new Girl(); 
 girl.speak(); 
…
     } 
 }

當(dāng)編譯 Party 類的時候旧噪,生成 girl.speak()的方法調(diào)用假設(shè)為:

Invokevirtual #12

設(shè)該調(diào)用代碼對應(yīng)著 girl.speak(); #12 是 Party 類的常量池的索引吨娜。JVM 執(zhí)行該調(diào)用指令的過程如下所示:

解析調(diào)用過程

JVM 首先查看 Party 的常量池索引為 12 的條目(應(yīng)為 CONSTANT_Methodref_info 類型,可視為方法調(diào)用的符號引用)淘钟,進(jìn)一步查看常量池(CONSTANT_Class_info宦赠,CONSTANT_NameAndType_info ,CONSTANT_Utf8_info)可得出要調(diào)用的方法是 Person 的 speak 方法(注意引用 girl 是其基類 Person 類型)米母,查看 Person 的方法表勾扭,得出 speak 方法在該方法表中的偏移量 15(offset),這就是該方法調(diào)用的直接引用铁瞒。

當(dāng)解析出方法調(diào)用的直接引用后(方法表偏移量 15)妙色,JVM 執(zhí)行真正的方法調(diào)用:根據(jù)實例方法調(diào)用的參數(shù) this 得到具體的對象(即 girl 所指向的位于堆中的對象),據(jù)此得到該對象對應(yīng)的方法表 (Girl 的方法表 )慧耍,進(jìn)而調(diào)用方法表中的某個偏移量所指向的方法(Girl 的 speak() 方法的實現(xiàn))身辨。

  • 接口調(diào)用
    因為 Java 類是可以同時實現(xiàn)多個接口的,而當(dāng)用接口引用調(diào)用某個方法的時候芍碧,情況就有所不同了煌珊。Java 允許一個類實現(xiàn)多個接口,從某種意義上來說相當(dāng)于多繼承泌豆,這樣同樣的方法在基類和派生類的方法表的位置就可能不一樣了定庵。
interface IDance{ 
   void dance(); 
 } 

 class Person { 
 public String toString(){ 
   return "I'm a person."; 
     } 
 public void eat(){} 
 public void speak(){} 
    
 } 

 class Dancer extends Person 
 implements IDance { 
 public String toString(){ 
   return "I'm a dancer."; 
     } 
 public void dance(){} 
 } 

 class Snake implements IDance{ 
 public String toString(){ 
   return "A snake."; 
     } 
 public void dance(){ 
 //snake dance 
     } 
 }
Dancer 的方法表

可以看到,由于接口的介入踪危,繼承自于接口 IDance 的方法 dance()在類 Dancer 和 Snake 的方法表中的位置已經(jīng)不一樣了蔬浙,顯然我們無法通過給出方法表的偏移量來正確調(diào)用 Dancer 和 Snake 的這個方法。這也是 Java 中調(diào)用接口方法有其專有的調(diào)用指令(invokeinterface)的原因陨倡。
Java 對于接口方法的調(diào)用是采用搜索方法表的方式敛滋,對如下的方法調(diào)用

invokeinterface #13

JVM 首先查看常量池许布,確定方法調(diào)用的符號引用(名稱兴革、返回值等等),然后利用 this 指向的實例得到該實例的方法表,進(jìn)而搜索方法表來找到合適的方法地址杂曲。
因為每次接口調(diào)用都要搜索方法表庶艾,所以從效率上來說,接口方法的調(diào)用總是慢于類方法的調(diào)用的擎勘。

吃圩幔考問題解析

下面來看一個關(guān)于多態(tài)的經(jīng)典實例
(一)相關(guān)類

public class A {
    public String show(D obj) {
        return ("A and D");
    }
 
    public String show(A obj) {
        return ("A and A");
    } 
 
}
 
public class B extends A{
    public String show(B obj){
        return ("B and B");
    }
 
    public String show(A obj){
        return ("B and A");
    } 
}
 
public class C extends B{
 
}
 
public class D extends B{
 
}

(二)問題:以下輸出結(jié)果是什么?

        A a1 = new A();
        A a2 = new B();
        B b = new B();
        C c = new C();
        D d = new D();
 
        System.out.println("1--" + a1.show(b));
        System.out.println("2--" + a1.show(c));
        System.out.println("3--" + a1.show(d));
        System.out.println("4--" + a2.show(b));
        System.out.println("5--" + a2.show(c));
        System.out.println("6--" + a2.show(d));
        System.out.println("7--" + b.show(b));
        System.out.println("8--" + b.show(c));
        System.out.println("9--" + b.show(d));  

(三)答案
1--A and A
2--A and A
3--A and D
4--B and A
5--B and A
6--A and D
7--B and B
8--B and B
9--A and D

(四)分析
①②③比較好理解棚饵,一般不會出錯煤裙。④⑤就有點糊涂了,為什么輸出的不是"B and B”呢噪漾?E鹋椤!先來回顧一下多態(tài)性欣硼。
運行時多態(tài)性是面向?qū)ο蟪绦蛟O(shè)計代碼重用的一個最強(qiáng)大機(jī)制题翰,動態(tài)性的概念也可以被說成“一個接口,多個方法”诈胜。Java實現(xiàn)運行時多態(tài)性的基礎(chǔ)是動態(tài)方法調(diào)度豹障,它是一種在運行時而不是在編譯期調(diào)用重載方法的機(jī)制。
方法的重寫Overriding和重載Overloading是Java多態(tài)性的不同表現(xiàn)焦匈。重寫Overriding是父類與子類之間多態(tài)性的一種表現(xiàn)血公,重載Overloading是一個類中多態(tài)性的一種表現(xiàn)。如果在子類中定義某方法與其父類有相同的名稱和參數(shù)缓熟,我們說該方法被重寫(Overriding)坞笙。子類的對象使用這個方法時,將調(diào)用子類中的定義荚虚,對它而言薛夜,父類中的定義如同被“屏蔽”了。如果在一個類中定義了多個同名的方法版述,它們或有不同的參數(shù)個數(shù)或有不同的參數(shù)類型梯澜,則稱為方法的重載(Overloading)。Overloaded的方法是可以改變返回值的類型渴析。
當(dāng)超類對象引用變量引用子類對象時晚伙,被引用對象的類型而不是引用變量的類型決定了調(diào)用誰的成員方法,但是這個被調(diào)用的方法必須是在超類中定義過的俭茧,也就是說被子類覆蓋的方法咆疗。 (但是如果強(qiáng)制把超類轉(zhuǎn)換成子類的話,就可以調(diào)用子類中新添加而超類沒有的方法了母债。)
好了午磁,先溫習(xí)到這里尝抖,言歸正傳!實際上這里涉及方法調(diào)用的優(yōu)先問題 迅皇,優(yōu)先級由高到低依次為:this.show(O)昧辽、super.show(O)、this.show((super)O)登颓、super.show((super)O)搅荞。讓我們來看看它是怎么工作的。
比如④框咙,a2.show(b)咕痛,a2是一個引用變量,類型為A喇嘱,則this為a2暇检,b是B的一個實例,于是它到類A里面找show(B obj)方法婉称,沒有找到块仆,于是到A的super(超類)找,而A沒有超類王暗,因此轉(zhuǎn)到第三優(yōu)先級this.show((super)O)悔据,this仍然是a2,這里O為B俗壹,(super)O即(super)B即A科汗,因此它到類A里面找show(A obj)的方法,類A有這個方法绷雏,但是由于a2引用的是類B的一個對象头滔,B覆蓋了A的show(A obj)方法,因此最終鎖定到類B的show(A obj)涎显,輸出為"B and A”坤检。
再比如⑧,b.show(c)期吓,b是一個引用變量早歇,類型為B,則this為b讨勤,c是C的一個實例箭跳,于是它到類B找show(C obj)方法,沒有找到潭千,轉(zhuǎn)而到B的超類A里面找谱姓,A里面也沒有,因此也轉(zhuǎn)到第三優(yōu)先級this.show((super)O)刨晴,this為b屉来,O為C路翻,(super)O即(super)C即B,因此它到B里面找show(B obj)方法奶躯,找到了,由于b引用的是類B的一個對象亿驾,因此直接鎖定到類B的show(B obj)嘹黔,輸出為"B and B”。
按照上面的方法莫瞬,可以正確得到其他的結(jié)果儡蔓。
問題還要繼續(xù),現(xiàn)在我們再來看上面的分析過程是怎么體現(xiàn)出藍(lán)色字體那句話的內(nèi)涵的疼邀。它說:當(dāng)超類對象引用變量引用子類對象時喂江,被引用對象的類型而不是引用變量的類型決定了調(diào)用誰的成員方法,但是這個被調(diào)用的方法必須是在超類中定義過的旁振,也就是說被子類覆蓋的方法获询。還是拿a2.show(b)來說吧。
a2是一個引用變量拐袜,類型為A吉嚣,它引用的是B的一個對象,因此這句話的意思是由B來決定調(diào)用的是哪個方法蹬铺。因此應(yīng)該調(diào)用B的show(B obj)從而輸出"B and B”才對尝哆。但是為什么跟前面的分析得到的結(jié)果不相符呢?甜攀!問題在于我們不要忽略了藍(lán)色字體的后半部分秋泄,那里特別指明:這個被調(diào)用的方法必須是在超類中定義過的,也就是被子類覆蓋的方法规阀。B里面的show(B obj)在超類A中有定義嗎恒序?沒有!那就更談不上被覆蓋了谁撼。實際上這句話隱藏了一條信息:它仍然是按照方法調(diào)用的優(yōu)先級來確定的奸焙。它在類A中找到了show(A obj),如果子類B沒有覆蓋show(A obj)方法彤敛,那么它就調(diào)用A的show(A obj)(由于B繼承A与帆,雖然沒有覆蓋這個方法,但從超類A那里繼承了這個方法墨榄,從某種意義上說玄糟,還是由B確定調(diào)用的方法,只是方法是在A中實現(xiàn)而已)袄秩;現(xiàn)在子類B覆蓋了show(A obj)阵翎,因此它最終鎖定到B的show(A obj)逢并。這就是那句話的意義所在。

參考文獻(xiàn)

[1]Java多態(tài)性理解
[2]java提高篇之理解java的三大特性——多態(tài)
[3]java多態(tài)實現(xiàn)原理
[4]深入理解java多態(tài)性

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末郭卫,一起剝皮案震驚了整個濱河市砍聊,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌贰军,老刑警劉巖玻蝌,帶你破解...
    沈念sama閱讀 217,185評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異词疼,居然都是意外死亡俯树,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評論 3 393
  • 文/潘曉璐 我一進(jìn)店門贰盗,熙熙樓的掌柜王于貴愁眉苦臉地迎上來许饿,“玉大人,你說我怎么就攤上這事舵盈÷剩” “怎么了?”我有些...
    開封第一講書人閱讀 163,524評論 0 353
  • 文/不壞的土叔 我叫張陵秽晚,是天一觀的道長翘贮。 經(jīng)常有香客問我,道長爆惧,這世上最難降的妖魔是什么狸页? 我笑而不...
    開封第一講書人閱讀 58,339評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮扯再,結(jié)果婚禮上芍耘,老公的妹妹穿的比我還像新娘。我一直安慰自己熄阻,他們只是感情好斋竞,可當(dāng)我...
    茶點故事閱讀 67,387評論 6 391
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著秃殉,像睡著了一般坝初。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上钾军,一...
    開封第一講書人閱讀 51,287評論 1 301
  • 那天鳄袍,我揣著相機(jī)與錄音,去河邊找鬼吏恭。 笑死拗小,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的樱哼。 我是一名探鬼主播哀九,決...
    沈念sama閱讀 40,130評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼剿配,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了阅束?” 一聲冷哼從身側(cè)響起呼胚,我...
    開封第一講書人閱讀 38,985評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎息裸,沒想到半個月后蝇更,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,420評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡界牡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,617評論 3 334
  • 正文 我和宋清朗相戀三年簿寂,在試婚紗的時候發(fā)現(xiàn)自己被綠了漾抬。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片宿亡。...
    茶點故事閱讀 39,779評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖纳令,靈堂內(nèi)的尸體忽然破棺而出挽荠,到底是詐尸還是另有隱情,我是刑警寧澤平绩,帶...
    沈念sama閱讀 35,477評論 5 345
  • 正文 年R本政府宣布圈匆,位于F島的核電站,受9級特大地震影響捏雌,放射性物質(zhì)發(fā)生泄漏跃赚。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,088評論 3 328
  • 文/蒙蒙 一性湿、第九天 我趴在偏房一處隱蔽的房頂上張望纬傲。 院中可真熱鬧,春花似錦肤频、人聲如沸叹括。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽汁雷。三九已至,卻和暖如春报咳,著一層夾襖步出監(jiān)牢的瞬間侠讯,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評論 1 269
  • 我被黑心中介騙來泰國打工暑刃, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留继低,地道東北人。 一個月前我還...
    沈念sama閱讀 47,876評論 2 370
  • 正文 我出身青樓稍走,卻偏偏與公主長得像袁翁,于是被迫代替她去往敵國和親柴底。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,700評論 2 354

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

  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法粱胜,類相關(guān)的語法柄驻,內(nèi)部類的語法,繼承相關(guān)的語法焙压,異常的語法鸿脓,線程的語...
    子非魚_t_閱讀 31,625評論 18 399
  • 一:java概述:1,JDK:Java Development Kit涯曲,java的開發(fā)和運行環(huán)境野哭,java的開發(fā)工...
    ZaneInTheSun閱讀 2,650評論 0 11
  • *面試心聲:其實這些題本人都沒怎么背,但是在上海 兩周半 面了大約10家 收到差不多3個offer,總結(jié)起來就是把...
    Dove_iOS閱讀 27,139評論 30 470
  • 為什么我需要年度目標(biāo) 美國哈佛大學(xué)有一個非常著名的關(guān)于目標(biāo)對人生影響的跟蹤調(diào)查拨黔。對象是一群智力、學(xué)歷绰沥、環(huán)境等條件差...
    亭主閱讀 5,135評論 15 237