java多態(tài)只能用在類似于 方法(父類) 嗎?而 父類 = new 子類 的意義是什么?

多態(tài)最大的作用就是為了傳參提供便利,但我們不應(yīng)該只看到這一層,還要往下再走走:為什么要用父類引用指向子類實(shí)例呢?

就好比你看到一把刀很鋒利,可以切菜,你不應(yīng)該疑惑“難道刀就是拿來切菜的嗎”,而應(yīng)該關(guān)注“為什么刀可以如此鋒利”...

回到你的問題上來,我們更應(yīng)該關(guān)心:為什么可以使用多態(tài)機(jī)制,以及為什么需要多態(tài)?

多態(tài)怎么實(shí)現(xiàn)的?

我并非計(jì)算機(jī)專業(yè),所以對于這個(gè)問題,只給出一個(gè)大概的解釋。多態(tài)從語法表面上看,就是子類對象可以賦值給父類引用,并且通過該引用可以動(dòng)態(tài)地調(diào)用不同子類的方法。

多態(tài)按實(shí)際用法又可以分為:

  • 繼承多態(tài)
  • 接口多態(tài)

所謂繼承多態(tài)(吸煙有害健康,我不抽煙):

class Son extends Father {
    @Overrid
    public void smoke() {
        System.out.print("兒子抽煙");
    }
}
class Daughter extends Father {
    @Overrid
    public void smoke() {
        System.out.print("女兒抽煙");
    }
}

// 繼承多態(tài),因?yàn)镾on、Daughter繼承了Father
Father obj = new Son();
obj.smoke(); // 打?。簝鹤映闊?obj = new Daughter();
obj.smoke(); // 打?。号畠撼闊?

所謂接口多態(tài):

class Son implements Swimmer {
    @Overrid
    public void swim() {
        System.out.print("兒子游泳");
    }
}
class Daughter implements Swimmer {
    @Overrid
    public void swim() {
        System.out.print("兒子游泳");
    }
}

// 接口多態(tài),因?yàn)镾on、Daughter實(shí)現(xiàn)了Swimmer
Swimmer obj = new Son();
obj.swim(); // 打?。簝鹤佑斡?obj = new Daughter();
obj.swim(); // 打?。号畠河斡?

實(shí)際開發(fā)接口多態(tài)更常用。

多態(tài)的實(shí)現(xiàn),依賴于2個(gè)大方面:

  • 機(jī)制上的支持
  • 編碼上的支持

機(jī)制支持

首先,編譯器要允許這種賦值方式,不然把son賦值給swimmer就會(huì)像把 int a賦值給String b一樣報(bào)錯(cuò)。

其次,運(yùn)行時(shí)要支持并且能通過某種機(jī)制找到真正的子類方法。

編碼支持

必須存在繼承(實(shí)現(xiàn))關(guān)系 + 子類必須重寫(實(shí)現(xiàn))父類的方法

我們一般所說的多態(tài),其實(shí)都是指方法的多態(tài)

什么意思呢?以上面Swimmer的代碼為例(假設(shè)整個(gè)工程只有這么幾個(gè)類),當(dāng)程序運(yùn)行時(shí),JVM中實(shí)際上并不存在一個(gè)對象叫Swimmer,自始至終只有Son和Daughter兩個(gè)對象,而且Son和Daughter都實(shí)現(xiàn)了Swimmer,且重寫了swim()方法。當(dāng)JVM運(yùn)行到:

image.png

JVM是怎么知道要打印“兒子游泳”的呢?換句話說,JVM怎么知道調(diào)用Son#swim()而不是Swimmer#swim()或者Daughter#swim()的呢?


image.png

這就涉及到所謂的“[虛方法]”和“虛方法表”了。JVM的知識點(diǎn)大概如下:


image.png

在類加載過程中,有l(wèi)oading、linking、initialization三個(gè)階段,其中l(wèi)inking(鏈接)階段又包括3個(gè)小階段:

  • verify(驗(yàn)證)
  • prepare(準(zhǔn)備)
  • resolve(解析)

其中在resolve階段,JVM會(huì)針對類或接口、字段、類方法、接口方法等進(jìn)行相應(yīng)解析,其中方法信息會(huì)形成所謂的“虛方法表”。

image.png

也就是說,當(dāng)出現(xiàn)多態(tài)方法調(diào)用時(shí),底層會(huì)多一次“查表”的過程,也就是通過搜索虛方法表,確定本次實(shí)際應(yīng)該調(diào)用的方法(實(shí)際指向?qū)ο?實(shí)例對應(yīng)的類有無重寫父類方法),如果子類Override了父類方法,那么就會(huì)執(zhí)行子類方法。

多態(tài)與設(shè)計(jì)模式

很多初學(xué)編程的人,一定會(huì)記住兩句話,即使他們并不懂得其中含義:

  • 面向?qū)ο蟮娜筇匦允牵悍庋b、繼承、多態(tài)
  • 萬物皆對象

但在我眼里,封裝、繼承這倆貨和多態(tài)根本不是一個(gè)檔次的(就好比李云迪和郎朗),多態(tài)才是面向?qū)ο蟮暮诵暮透?,甚至沒有多態(tài)就沒有面向?qū)ο蟆?/strong>舉個(gè)例子,C語言沒有封裝嗎?不也是可以抽取方法嗎?也有結(jié)構(gòu)體呢,看起來不像對象嗎?再者,你問問自己,你使用繼承是為了什么?不就是為了貪圖父類的那一點(diǎn)點(diǎn)已經(jīng)寫好的方法,為了偷點(diǎn)懶嗎?既然是為了少寫一點(diǎn)代碼,我抽取成方法不行嗎?

所以,到底什么是面向?qū)ο竽兀?/p>

這就回到了我上面說的,多態(tài)才是面向?qū)ο蟮暮诵模ó?dāng)然,面向?qū)ο蟊举|(zhì)是一種編程思想的轉(zhuǎn)變)。當(dāng)我們有了多態(tài),才能寫出更加抽象的代碼,而抽象代表穩(wěn)定。

假設(shè)世界末日,外星人占領(lǐng)地球了,它們覺得必須殺雞儆猴,我們因?yàn)檎娴拇虿贿^,只能任由宰割。此時(shí)我們簽訂契約:你們可以殺一個(gè)動(dòng)物。于是我們送了一只實(shí)驗(yàn)室的小白鼠,因?yàn)樾“资笠彩莿?dòng)物呀。動(dòng)物這個(gè)詞是抽象的,后面我們送啥都可以,只要不送人。

再舉個(gè)編程的例子。假設(shè)在寫好的一個(gè)類文件中,你寫下這樣一段代碼:


image.png

如果后期接入拼多多,你就需要修改代碼。但如果使用策略模式,就可以用增量的方式代替修改(開閉原則):

image.png
image.png

具體可以參考:優(yōu)化代碼中大量的if/else,你有什么方案?

沒錯(cuò),這就是策略模式。而所謂的設(shè)計(jì)模式,其實(shí)有一本書的書名,恰恰點(diǎn)破了設(shè)計(jì)模式的本質(zhì):

image.png

是的,設(shè)計(jì)模式本質(zhì)是圍繞著“在面向?qū)ο蟮幕A(chǔ)上,如何復(fù)用設(shè)計(jì)”這個(gè)原則展開的...所以本質(zhì)又回到了面向?qū)ο蟆?/p>

為什么設(shè)計(jì)模式這么牛逼,能把很多看起來像“屎山一樣”的代碼優(yōu)化得清晰、簡潔?本質(zhì)上就是多態(tài)!

所謂“屎山一樣”的代碼,大概率就是因?yàn)楹笃谛枨蟛粩嗟?,開發(fā)人員在未經(jīng)思考的情況下肆意使用if else添加邏輯分支導(dǎo)致的!但分支是不會(huì)無緣無故消失的,只是借助設(shè)計(jì)模式把分支下推,最終交給了多態(tài)——JVM,你給我去查虛方法表。

換句話說就是:JVM,這坨屎你來吃。

最終,JVM帶著虛方法表承受了一切,而我們的上層代碼一掃陰霾,看起來干凈而整潔,也就是所謂的clean code...

所以,最后再問一句:多態(tài)真的就是用來傳參嗎?

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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

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