Java多態(tài)實現(xiàn)原理

Java多態(tài)概述

多態(tài)是面向?qū)ο缶幊陶Z言的重要特性,它允許基類的指針或引用指向派生類的對象素挽,而在具體訪問時實現(xiàn)方法的動態(tài)綁定褐捻。Java 對于方法調(diào)用動態(tài)綁定的實現(xiàn)主要依賴于方法表渐尿,但通過類引用調(diào)用(invokevirtual)和接口引用調(diào)用(invokeinterface)的實現(xiàn)則有所不同。

類引用調(diào)用的大致過程為:Java編譯器將Java源代碼編譯成class文件捕犬,在編譯過程中跷坝,會根據(jù)靜態(tài)類型將調(diào)用的符號引用寫到class文件中。在執(zhí)行時碉碉,JVM根據(jù)class文件找到調(diào)用方法的符號引用柴钻,然后在靜態(tài)類型的方法表中找到偏移量,然后根據(jù)this指針確定對象的實際類型垢粮,使用實際類型的方法表贴届,偏移量跟靜態(tài)類型中方法表的偏移量一樣,如果在實際類型的方法表中找到該方法,則直接調(diào)用毫蚓,否則占键,認(rèn)為沒有重寫父類該方法。按照繼承關(guān)系從下往上搜索元潘。

接口引用調(diào)用后面再說吧畔乙。



從上圖可以看出,當(dāng)程序運行時翩概,需要某個類時牲距,類載入子系統(tǒng)會將相應(yīng)的class文件載入到JVM中,并在內(nèi)部建立該類的類型信息(這個類型信息其實就是class文件在JVM中存儲的一種數(shù)據(jù)結(jié)構(gòu))钥庇,包含java類定義的所有信息牍鞠,包括方法代碼,類變量评姨、成員變量难述、以及本博文要重點討論的方法表。這個類型信息就存儲在方法區(qū)吐句。

注意胁后,這個方法區(qū)中的類型信息跟在堆中存放的class對象是不同的。在方法區(qū)中蕴侧,這個class的類型信息只有唯一的實例(所以是各個線程共享的內(nèi)存區(qū)域)择同,而在堆中可以有多個該class對象两入【幌可以通過堆中的class對象訪問到方法區(qū)中類型信息。就像在java反射機制那樣裹纳,通過class對象可以訪問到該類的所有信息一樣择葡。
【重點】
方法表是實現(xiàn)動態(tài)調(diào)用的核心。上面講過方法表存放在方法區(qū)中的類型信息中剃氧。為了優(yōu)化對象調(diào)用方法的速度敏储,方法區(qū)的類型信息會增加一個指針,該指針指向一個記錄該類方法的方法表朋鞍,方法表中的每一個項都是對應(yīng)方法的指針已添。
這些方法中包括從父類繼承的所有方法以及自身重寫(override)的方法。
【拓展】
方法區(qū):方法區(qū)和JAVA堆一樣滥酥,是各個線程共享的內(nèi)存區(qū)域更舞,用于存儲已被虛擬機加載的類信息、常量坎吻、靜態(tài)變量缆蝉、即時編譯器編譯后的代碼等數(shù)據(jù)。
運行時常量池:它是方法區(qū)的一部分,Class文件中除了有類的版本刊头、方法黍瞧、字段等描述信息外,還有一項信息是常量池原杂,用于存放編譯器生成的各種符號引用印颤,這部分信息在類加載時進入方法區(qū)的運行時常量池中。
方法區(qū)的內(nèi)存回收目標(biāo)是針對常量池的回收及對類型的卸載污尉。

Java 的方法調(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)要解決的核心問題焚志。
    JVM 的方法調(diào)用指令有四個衣迷,分別是 invokestatic,invokespecial酱酬,invokesvirtual 和 invokeinterface壶谒。前兩個是靜態(tài)綁定,后兩個是動態(tài)綁定的膳沽。本文也可以說是對于JVM后兩種調(diào)用實現(xiàn)的考察汗菜。
方法表與方法調(diào)用

如有類定義 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 虛擬機之后,方法區(qū)中就包含了各自的類的信息挑社。Girl 和 Boy 在方法區(qū)中的方法表可表示如下:

可以看到陨界,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)用指令的過程如下所示:


(1)在常量池(這里有個錯誤链瓦,上圖為ClassReference常量池而非Party的常量池)中找到方法調(diào)用的符號引用 。 (2)查看Person的方法表盯桦,得到speak方法在該方法表的偏移量(假設(shè)為15)慈俯,這樣就得到該方法的直接引用。
(3)根據(jù)this指針得到具體的對象(即 girl 所指向的位于堆中的對象)拥峦。
(4)根據(jù)對象得到該對象對應(yīng)的方法表贴膘,根據(jù)偏移量15查看有無重寫(override)該方法,如果重寫略号,則可以直接調(diào)用(Girl的方法表的speak項指向自身的方法而非父類)刑峡;如果沒有重寫,則需要拿到按照繼承關(guān)系從下往上的基類(這里是Person類)的方法表玄柠,同樣按照這個偏移量15查看有無該方法突梦。

接口調(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  
    }
}

可以看到娃闲,由于接口的介入,繼承自于接口 IDance 的方法 dance()在類 Dancer 和 Snake 的方法表中的位置已經(jīng)不一樣了当宴,顯然我們無法僅根據(jù)偏移量來進行方法的調(diào)用畜吊。

Java 對于接口方法的調(diào)用是采用搜索方法表的方式泽疆,如户矢,要在Dancer的方法表中找到dance()方法,必須搜索Dancer的整個方法表殉疼。

因為每次接口調(diào)用都要搜索方法表梯浪,所以從效率上來說,接口方法的調(diào)用總是慢于類方法的調(diào)用的瓢娜。

參考文章:
java動態(tài)綁定機制內(nèi)幕
深入理解java多態(tài)
Java多態(tài)實現(xiàn)原理

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末挂洛,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子眠砾,更是在濱河造成了極大的恐慌虏劲,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,029評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異柒巫,居然都是意外死亡励堡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,395評論 3 385
  • 文/潘曉璐 我一進店門堡掏,熙熙樓的掌柜王于貴愁眉苦臉地迎上來应结,“玉大人,你說我怎么就攤上這事泉唁《炝洌” “怎么了?”我有些...
    開封第一講書人閱讀 157,570評論 0 348
  • 文/不壞的土叔 我叫張陵亭畜,是天一觀的道長扮休。 經(jīng)常有香客問我,道長拴鸵,這世上最難降的妖魔是什么肛炮? 我笑而不...
    開封第一講書人閱讀 56,535評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮宝踪,結(jié)果婚禮上侨糟,老公的妹妹穿的比我還像新娘。我一直安慰自己瘩燥,他們只是感情好秕重,可當(dāng)我...
    茶點故事閱讀 65,650評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著厉膀,像睡著了一般溶耘。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上服鹅,一...
    開封第一講書人閱讀 49,850評論 1 290
  • 那天凳兵,我揣著相機與錄音,去河邊找鬼企软。 笑死庐扫,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的仗哨。 我是一名探鬼主播形庭,決...
    沈念sama閱讀 39,006評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼厌漂!你這毒婦竟也來了萨醒?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,747評論 0 268
  • 序言:老撾萬榮一對情侶失蹤苇倡,失蹤者是張志新(化名)和其女友劉穎富纸,沒想到半個月后囤踩,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,207評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡晓褪,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,536評論 2 327
  • 正文 我和宋清朗相戀三年高职,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片辞州。...
    茶點故事閱讀 38,683評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡怔锌,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出变过,到底是詐尸還是另有隱情埃元,我是刑警寧澤,帶...
    沈念sama閱讀 34,342評論 4 330
  • 正文 年R本政府宣布媚狰,位于F島的核電站岛杀,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏崭孤。R本人自食惡果不足惜类嗤,卻給世界環(huán)境...
    茶點故事閱讀 39,964評論 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望辨宠。 院中可真熱鬧遗锣,春花似錦、人聲如沸嗤形。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,772評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽赋兵。三九已至笔咽,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間霹期,已是汗流浹背叶组。 一陣腳步聲響...
    開封第一講書人閱讀 32,004評論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留历造,地道東北人甩十。 一個月前我還...
    沈念sama閱讀 46,401評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像帕膜,于是被迫代替她去往敵國和親枣氧。 傳聞我的和親對象是個殘疾皇子溢十,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,566評論 2 349

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

  • 轉(zhuǎn)自http://blog.csdn.net/huangrunqing/article/details/51996...
    seawish閱讀 678評論 0 0
  • 面向?qū)ο笾饕槍γ嫦蜻^程垮刹。 面向過程的基本單元是函數(shù)。 什么是對象:EVERYTHING IS OBJECT(萬物...
    sinpi閱讀 1,046評論 0 4
  • 1.import static是Java 5增加的功能,就是將Import類中的靜態(tài)方法张弛,可以作為本類的靜態(tài)方法來...
    XLsn0w閱讀 1,217評論 0 2
  • 「1」 我有兩個胞弟遮咖,每每提及滩字,有人驚訝有人羨慕,畢竟我這個年紀(jì)御吞,還是在計劃生育...
    阿羊ai閱讀 754評論 1 0
  • 想投身一場奮不顧身的愛情麦箍,我也想要肆意的笑,肆意的哭陶珠,就算滿身傷痕挟裂,賠上我的命,也絕不后悔揍诽! 第一回坐大巴座位第一...
    視人頃刻間閱讀 138評論 0 0