Java 多態(tài)與動(dòng)態(tài)綁定

class Animal {
    public void cry() {
        System.out.println("xxx");
    }
}

class Cat extends Animal {
    public void cry() {
        System.out.println("yyy");
    }

    public void bite() {
        System.out.println("zzz");
    }
}

public class PolymorphicTest {
    public static void main(String ...args) {
        Animal cat = new Cat();
        cat.cry();                  // yyy
        ((Cat)cat).bite();          // zzz
    }
}

多態(tài)存在的三個(gè)必要條件:繼承、重寫、父類變量引用子類對(duì)象

數(shù)據(jù)轉(zhuǎn)型

  • 向上轉(zhuǎn)型(up casting):就是將子類的對(duì)象轉(zhuǎn)型成父類的引用(就是父類引用指向了子類的對(duì)象,此時(shí)此引用只能訪問父類的成員)。
  • 向下轉(zhuǎn)型(down casting):將向上轉(zhuǎn)型的引用再轉(zhuǎn)回來,就叫向下轉(zhuǎn)型(此時(shí)引用能訪問子類的成員變量)

多態(tài)中成員的特點(diǎn)

  • 在多態(tài)中成員函數(shù)的特點(diǎn):動(dòng)態(tài)綁定
    在編譯時(shí)期:參閱引用型變量所屬的類中是否有調(diào)用的方法。如果有,編譯通過,如果沒有編譯失敗
    在運(yùn)行時(shí)期:參閱對(duì)象所屬的類中是否有調(diào)用的方法
    簡(jiǎn)單總結(jié)就是:成員函數(shù)在多態(tài)調(diào)用時(shí),編譯看左邊,運(yùn)行看右邊

  • 在多態(tài)中,成員變量的特點(diǎn):靜態(tài)綁定
    無論編譯和運(yùn)行,都參考左邊(引用型變量所屬的類)

  • 在多態(tài)中,靜態(tài)成員函數(shù)的特點(diǎn):靜態(tài)綁定
    無論編譯和運(yùn)行,都參考左邊
    因?yàn)殪o態(tài)只看類,沒有重寫,只有隱藏

多態(tài)好處

不用修改源程序代碼,就可以讓引用變量綁定到各種不同的類實(shí)現(xiàn)上,從而導(dǎo)致該引用調(diào)用的具體方法隨之改變,即不修改程序代碼就可以改變程序運(yùn)行時(shí)所綁定的具體代碼,讓程序可以選擇多個(gè)運(yùn)行狀態(tài),這就是多態(tài)性。多態(tài)性的實(shí)現(xiàn)主要通過動(dòng)態(tài)綁定

程序綁定

程序綁定是一個(gè)成員的調(diào)用與成員所在的類(成員主體)關(guān)聯(lián)起來。對(duì) Java 來說,綁定分為靜態(tài)綁定和動(dòng)態(tài)綁定

靜態(tài)綁定(前期綁定)

在編譯期已經(jīng)可以確定的信息,就在程序執(zhí)行前被綁定,此時(shí)由編譯器或其它連接程序?qū)崿F(xiàn)。針對(duì) Java 簡(jiǎn)單的可以理解為程序編譯期的綁定,綁定的是類信息

  • Java 中的方法只有 final,static,private,重載方法和構(gòu)造方法是靜態(tài)綁定
  • 所有的變量都是靜態(tài)綁定

動(dòng)態(tài)綁定(后期綁定)

編譯器在編譯階段不知道要調(diào)用哪個(gè)方法,在運(yùn)行時(shí)根據(jù)具體對(duì)象的類型進(jìn)行綁定,綁定的是對(duì)象信息

若一種語言實(shí)現(xiàn)了后期綁定,同時(shí)必須提供一些機(jī)制,可在運(yùn)行期間判斷對(duì)象的類型,并分別調(diào)用適當(dāng)?shù)姆椒?。也就是說,編譯器此時(shí)依然不知道對(duì)象的類型,但方法調(diào)用機(jī)制能自己去調(diào)查,找到正確的方法主體。不同的語言對(duì)后期綁定的實(shí)現(xiàn)方法是有所區(qū)別的。但我們至少可以這樣認(rèn)為:它們都要在對(duì)象中安插某些特殊類型的信息

重寫方法使用的是動(dòng)態(tài)綁定

方法調(diào)用

以 obj.func(param) 為例,隱式參數(shù) obj 聲明為 Cat 類的對(duì)象

  1. 編譯器查看對(duì)象的聲明類型和方法名
    需要注意的是,有可能存在多個(gè)名字為 func 但參數(shù)簽名不一樣的方法。例如,可能存在方法 func(int) 和 func(String)。編譯器將會(huì)一一列舉所有 Cat 類中名為 func 的方法和其父類 Animal 中訪問屬性為 public 且名為 func 的方法(超類中的私有方法不可訪問)
    這樣,編譯器就獲得了所有可能被調(diào)用的候選方法列表
  1. 接下來,編譯器將檢查調(diào)用方法時(shí)提供的參數(shù)簽名
    如果在所有名為 func 的方法中存在一個(gè)與提供的參數(shù)簽名完全匹配的方法,那么就選擇這個(gè)方法。這個(gè)過程被稱為重載解析(overloading resolution)。例如,如果調(diào)用 func("hello"),編譯器會(huì)選擇 func(String),而不是 func(int)。由于自動(dòng)類型轉(zhuǎn)換的存在,例如 int 可以轉(zhuǎn)換為 double,Cat 可以轉(zhuǎn)換成 Animal 等,如果沒有找到與調(diào)用方法參數(shù)簽名匹配的方法,就進(jìn)行類型轉(zhuǎn)換后再繼續(xù)查找。如果編譯器沒有找到與參數(shù)類型匹配的方法,或者發(fā)現(xiàn)經(jīng)過類型轉(zhuǎn)換后有多個(gè)方法與之匹配,那么編譯錯(cuò)誤
    這樣,編譯器就獲得了需要調(diào)用的方法名字和參數(shù)類型

  2. 如果是 private、static、final,或者是構(gòu)造方法,那么編譯器將可以準(zhǔn)確地知道應(yīng)該調(diào)用哪個(gè)方法,這種調(diào)用方式稱為靜態(tài)綁定。與此對(duì)應(yīng)的是,調(diào)用的方法依賴于隱式參數(shù)的實(shí)際類型,這就需要在運(yùn)行時(shí)通過動(dòng)態(tài)綁定確定調(diào)用方法,比如上面的 cat.cry()

  3. 當(dāng)程序運(yùn)行,并且釆用動(dòng)態(tài)綁定調(diào)用方法時(shí),JVM 一定會(huì)調(diào)用與 obj 所引用對(duì)象的實(shí)際類型最合適的那個(gè)類的方法。我們已經(jīng)假設(shè) obj 的實(shí)際類型是 Cat,它是 Animal 的子類,如果 Cat 中定義了 func(String),就調(diào)用它,否則將在 Animal 類及其父類中尋找

方法表

每次調(diào)用方法都要進(jìn)行搜索,時(shí)間開銷相當(dāng)大,因此,JVM 預(yù)先為每個(gè)類創(chuàng)建了一個(gè)方法表,其中列出了所有方法的名稱、參數(shù)簽名和所屬的類。這樣一來,在真正調(diào)用方法的時(shí)候,JVM 僅查找這個(gè)表就行了。在上面的例子中,JVM 搜索 Cat 類的方法表,以便尋找與調(diào)用 func("hello") 相匹配的方法。這個(gè)方法既有可能是 Cat.func(String),也有可能是 Animal.func(String)。注意,如果調(diào)用 super.func("hello"),編譯器將對(duì)父類的方法表進(jìn)行搜索

假設(shè) Animal 類包含 cry()、getName()、getAge() 三個(gè)方法,那么它的方法表如下:

cry() -> Animal.cry()
getName() -> Animal.getName()
getAge() -> Animal.getAge()

實(shí)際上,Animal 有默認(rèn)的父類 Object,會(huì)繼承 Object 的方法,所以上面列舉的方法并不完整

假設(shè) Cat 類覆蓋了 Animal 類中的 cry() 方法,并且新增了一個(gè)方法 climbTree(),那么它的參數(shù)列表為:

cry() -> Cat.cry()
getName() -> Animal.getName()
getAge() -> Animal.getAge()
climbTree() -> Cat.climbTree()

在運(yùn)行的時(shí)候,調(diào)用 obj.cry() 方法的過程如下:

  • JVM 首先提取 obj 的實(shí)際類型的方法表,可能是 Animal 類的方法表,也可能是 Cat 類及其子類的方法表
  • JVM 在方法表中搜索與 cry() 簽名匹配的方法,找到后,就知道它屬于哪個(gè)類了
  • JVM 調(diào)用該方法

總結(jié)

  • 先通過對(duì)象類型與方法名在方法表中找
  • 通過參數(shù)簽名進(jìn)行重載解析
  • 確認(rèn)屬于靜態(tài)綁定還是動(dòng)態(tài)綁定,最后調(diào)用
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容