类对接口的实现不叫继承_Java多态的实现机制是什么,看完你就知道(值得收藏)...
Java多態(tài)的實(shí)現(xiàn)機(jī)制是父類(lèi)或接口定義的引用變量可以指向子類(lèi)或?qū)崿F(xiàn)類(lèi)的實(shí)例對(duì)象,而程序調(diào)用的方法在運(yùn)行期才動(dòng)態(tài)綁定,就是引用變量所指向的具體實(shí)現(xiàn)對(duì)象的方法,也就是內(nèi)存里正在運(yùn)行的那個(gè)對(duì)象的方法,而不是引用變量的類(lèi)型中定義的方法。
淺談多態(tài)機(jī)制的意義及實(shí)現(xiàn)
在面向?qū)ο缶幊?Object-Oriented Programming, OOP)中,多態(tài)機(jī)制無(wú)疑是其最具特色的功能,甚至可以說(shuō),不運(yùn)用多態(tài)的編程不能稱(chēng)之為OOP。這也是為什么有人說(shuō),使用面向?qū)ο笳Z(yǔ)言的編程和面向?qū)ο蟮木幊淌莾纱a事。
多態(tài)并沒(méi)有一個(gè)嚴(yán)格的定義,維基百科上給它下的定義比較寬松:
Subtype polymorphism, almost universally called just polymorphism in the context of object-oriented programming, is the ability of one type, A, to appear as and be used like another type, B.
一、子類(lèi)型和子類(lèi)
這里我想先提一下子類(lèi)型(Subtype)這個(gè)詞和子類(lèi)(Subclass)的區(qū)別,簡(jiǎn)單地說(shuō),只要是A類(lèi)運(yùn)用了extends關(guān)鍵字實(shí)現(xiàn)了對(duì)B類(lèi)的繼承,那么我們就可以說(shuō)Class A是Class B的子類(lèi),子類(lèi)是一個(gè)語(yǔ)法層面上的詞,只要滿足繼承的語(yǔ)法,就存在子類(lèi)關(guān)系。
子類(lèi)型比子類(lèi)有更嚴(yán)格的要求,它不僅要求有繼承的語(yǔ)法,同時(shí)要求如果存在子類(lèi)對(duì)父類(lèi)方法的改寫(xiě)(override),那么改寫(xiě)的內(nèi)容必須符合父類(lèi)原本的語(yǔ)義,其被調(diào)用后的作用應(yīng)該和父類(lèi)實(shí)現(xiàn)的效果方向一致。
對(duì)二者的對(duì)比是想強(qiáng)調(diào)一點(diǎn):只有保證子類(lèi)都是子類(lèi)型,多態(tài)才有意義。Java父類(lèi)強(qiáng)制轉(zhuǎn)換子類(lèi)原則。
二、多態(tài)的機(jī)制
本質(zhì)上多態(tài)分兩種:
重載(overload)就是編譯時(shí)多態(tài)的一個(gè)例子,編譯時(shí)多態(tài)在編譯時(shí)就已經(jīng)確定,運(yùn)行時(shí)運(yùn)行的時(shí)候調(diào)用的是確定的方法。
我們通常所說(shuō)的多態(tài)指的都是運(yùn)行時(shí)多態(tài),也就是編譯時(shí)不確定究竟調(diào)用哪個(gè)具體方法,一直延遲到運(yùn)行時(shí)才能確定。這也是為什么有時(shí)候多態(tài)方法又被稱(chēng)為延遲方法的原因。
在維基百科中多態(tài)的行為被描述為:
The primary usage of polymorphism in industry (object-oriented programming theory) is the ability of objects belonging to different types to respond to method, field, or property calls of the same name, each one according to an appropriate type-specific behavior.
下面簡(jiǎn)要介紹一下運(yùn)行時(shí)多態(tài)(以下簡(jiǎn)稱(chēng)多態(tài))的機(jī)制。
多態(tài)通常有兩種實(shí)現(xiàn)方法:
無(wú)論是哪種方法,其核心之處就在于對(duì)父類(lèi)方法的改寫(xiě)或?qū)涌诜椒ǖ膶?shí)現(xiàn),以取得在運(yùn)行時(shí)不同的執(zhí)行效果。
要使用多態(tài),在聲明對(duì)象時(shí)就應(yīng)該遵循一條法則:聲明的總是父類(lèi)類(lèi)型或接口類(lèi)型,創(chuàng)建的是實(shí)際類(lèi)型。舉例來(lái)說(shuō),假設(shè)我們要?jiǎng)?chuàng)建一個(gè)ArrayList對(duì)象,聲明就應(yīng)該采用這樣的語(yǔ)句:
List list = newArrayList();而不是
ArrayList list = newArrayList();在定義方法參數(shù)時(shí)也通常總是應(yīng)該優(yōu)先使用父類(lèi)類(lèi)型或接口類(lèi)型,例如某方法應(yīng)該寫(xiě)成:
public void doSomething(List list);而不是
public void doSomething(ArrayList list);這樣聲明最大的好處在于結(jié)構(gòu)的靈活性:假如某一天我認(rèn)為ArrayList的特性無(wú)法滿足我的要求,我希望能夠用LinkedList來(lái)代替它,那么只需要在對(duì)象創(chuàng)建的地方把new ArrayList()改為new LinkedList即可,其它代碼一概不用改動(dòng)。
The programmer (and the program) does not have to know the exact type of the object in advance, and so the exact behavior is determined at run-time (this is called late binding or dynamic binding).
虛擬機(jī)會(huì)在執(zhí)行程序時(shí)動(dòng)態(tài)調(diào)用實(shí)際類(lèi)的方法,它會(huì)通過(guò)一種名為動(dòng)態(tài)綁定(又稱(chēng)延遲綁定)的機(jī)制自動(dòng)實(shí)現(xiàn),這個(gè)過(guò)程對(duì)程序員來(lái)說(shuō)是透明的。
三、多態(tài)的用途
多態(tài)最大的用途我認(rèn)為在于對(duì)設(shè)計(jì)和架構(gòu)的復(fù)用,更進(jìn)一步來(lái)說(shuō),《設(shè)計(jì)模式》中提倡的針對(duì)接口編程而不是針對(duì)實(shí)現(xiàn)編程就是充分利用多態(tài)的典型例子。
定義功能和組件時(shí)定義接口,實(shí)現(xiàn)可以留到之后的流程中。同時(shí)一個(gè)接口可以有多個(gè)實(shí)現(xiàn),甚至于完全可以在一個(gè)設(shè)計(jì)中同時(shí)使用一個(gè)接口的多種實(shí)現(xiàn)(例如針對(duì)ArrayList和LinkedList不同的特性決定究竟采用哪種實(shí)現(xiàn))。
四、多態(tài)的實(shí)現(xiàn)
下面從虛擬機(jī)運(yùn)行時(shí)的角度來(lái)簡(jiǎn)要介紹多態(tài)的實(shí)現(xiàn)原理,這里以Java虛擬機(jī)(Java Virtual Machine, JVM)規(guī)范的實(shí)現(xiàn)為例。JVM 與 Linux 的內(nèi)存關(guān)系詳解。
在JVM執(zhí)行Java字節(jié)碼時(shí),類(lèi)型信息被存放在方法區(qū)中,通常為了優(yōu)化對(duì)象調(diào)用方法的速度,方法區(qū)的類(lèi)型信息中增加一個(gè)指針,該指針指向一張記錄該類(lèi)方法入口的表(稱(chēng)為方法表),表中的每一項(xiàng)都是指向相應(yīng)方法的指針。
方法表的構(gòu)造如下:
由于Java的單繼承機(jī)制,一個(gè)類(lèi)只能繼承一個(gè)父類(lèi),而所有的類(lèi)又都繼承自O(shè)bject類(lèi)。方法表中最先存放的是Object類(lèi)的方法,接下來(lái)是該類(lèi)的父類(lèi)的方法,最后是該類(lèi)本身的方法。這里關(guān)鍵的地方在于,如果子類(lèi)改寫(xiě)了父類(lèi)的方法,那么子類(lèi)和父類(lèi)的那些同名方法共享一個(gè)方法表項(xiàng),都被認(rèn)作是父類(lèi)的方法。
注意這里只有非私有的實(shí)例方法才會(huì)出現(xiàn),并且靜態(tài)方法也不會(huì)出現(xiàn)在這里,原因很容易理解:靜態(tài)方法跟對(duì)象無(wú)關(guān),可以將方法地址直接引用,而不像實(shí)例方法需要間接引用。
更深入地講,靜態(tài)方法是由虛擬機(jī)指令invokestatic調(diào)用的,私有方法和構(gòu)造函數(shù)則是由invokespecial指令調(diào)用,只有被invokevirtual和invokeinterface指令調(diào)用的方法才會(huì)在方法表中出現(xiàn)。
由于以上方法的排列特性(Object——父類(lèi)——子類(lèi)),使得方法表的偏移量總是固定的。例如,對(duì)于任何類(lèi)來(lái)說(shuō),其方法表中equals方法的偏移量總是一個(gè)定值,所有繼承某父類(lèi)的子類(lèi)的方法表中,其父類(lèi)所定義的方法的偏移量也總是一個(gè)定值。
前面說(shuō)過(guò),方法表中的表項(xiàng)都是指向該類(lèi)對(duì)應(yīng)方法的指針,這里就開(kāi)始了多態(tài)的實(shí)現(xiàn):
假設(shè)Class A是Class B的子類(lèi),并且A改寫(xiě)了B的方法method(),那么在B的方法表中,method方法的指針指向的就是B的method方法入口。
而對(duì)于A來(lái)說(shuō),它的方法表中的method方法則會(huì)指向其自身的method方法而非其父類(lèi)的(這在類(lèi)加載器載入該類(lèi)時(shí)已經(jīng)保證,同時(shí)JVM會(huì)保證總是能從對(duì)象引用指向正確的類(lèi)型信息)。
結(jié)合方法指針偏移量是固定的以及指針總是指向?qū)嶋H類(lèi)的方法域,我們不難發(fā)現(xiàn)多態(tài)的機(jī)制就在這里:
在調(diào)用方法時(shí),實(shí)際上必須首先完成實(shí)例方法的符號(hào)引用解析,結(jié)果是該符號(hào)引用被解析為方法表的偏移量。
虛擬機(jī)通過(guò)對(duì)象引用得到方法區(qū)中類(lèi)型信息的入口,查詢類(lèi)的方法表,當(dāng)將子類(lèi)對(duì)象聲明為父類(lèi)類(lèi)型時(shí),形式上調(diào)用的是父類(lèi)方法,此時(shí)虛擬機(jī)會(huì)從實(shí)際類(lèi)的方法表(雖然聲明的是父類(lèi),但是實(shí)際上這里的類(lèi)型信息中存放的是子類(lèi)的信息)中查找該方法名對(duì)應(yīng)的指針(這里用“查找”實(shí)際上是不合適的,前面提到過(guò),方法的偏移量是固定的,所以只需根據(jù)偏移量就能獲得指針),進(jìn)而就能指向?qū)嶋H類(lèi)的方法了。
我們的故事還沒(méi)有結(jié)束,事實(shí)上上面的過(guò)程僅僅是利用繼承實(shí)現(xiàn)多態(tài)的內(nèi)部機(jī)制,多態(tài)的另外一種實(shí)現(xiàn)方式:實(shí)現(xiàn)接口相比而言就更加復(fù)雜,原因在于,Java的單繼承保證了類(lèi)的線性關(guān)系,而接口可以同時(shí)實(shí)現(xiàn)多個(gè),這樣光憑偏移量就很難準(zhǔn)確獲得方法的指針。所以在JVM中,多態(tài)的實(shí)例方法調(diào)用實(shí)際上有兩種指令:
- invokevirtual指令用于調(diào)用聲明為類(lèi)的方法;
- invokeinterface指令用于調(diào)用聲明為接口的方法。
當(dāng)使用invokeinterface指令調(diào)用方法時(shí),就不能采用固定偏移量的辦法,只能老老實(shí)實(shí)挨個(gè)找了(當(dāng)然實(shí)際實(shí)現(xiàn)并不一定如此,JVM規(guī)范并沒(méi)有規(guī)定究竟如何實(shí)現(xiàn)這種查找,不同的JVM實(shí)現(xiàn)可以有不同的優(yōu)化算法來(lái)提高搜索效率)。
我們不難看出,在性能上,調(diào)用接口引用的方法通常總是比調(diào)用類(lèi)的引用的方法要慢。這也告訴我們,在類(lèi)和接口之間優(yōu)先選擇接口作為設(shè)計(jì)并不總是正確的,當(dāng)然設(shè)計(jì)問(wèn)題不在本文探討的范圍之內(nèi),但顯然具體問(wèn)題具體分析仍然不失為更好的選擇。
個(gè)人見(jiàn)解:多態(tài)機(jī)制包括靜態(tài)多態(tài)(編譯時(shí)多態(tài))和動(dòng)態(tài)多態(tài)(運(yùn)行時(shí)多態(tài)),靜態(tài)多態(tài)比如說(shuō)重載,動(dòng)態(tài)多態(tài)是在編譯時(shí)不能確定調(diào)用哪個(gè)方法,得在運(yùn)行時(shí)確定。動(dòng)態(tài)多態(tài)的實(shí)現(xiàn)方法包括子類(lèi)繼承父類(lèi)和類(lèi)實(shí)現(xiàn)接口。當(dāng)多個(gè)子類(lèi)上轉(zhuǎn)型(不知道這么說(shuō)對(duì)不)時(shí),對(duì)象掉用的是相應(yīng)子類(lèi)的方法,這種實(shí)現(xiàn)是與JVM有關(guān)的。
end:如果你覺(jué)得本文對(duì)你有幫助的話,記得點(diǎn)贊轉(zhuǎn)發(fā),你的支持就是我更新動(dòng)力。
總結(jié)
以上是生活随笔為你收集整理的类对接口的实现不叫继承_Java多态的实现机制是什么,看完你就知道(值得收藏)...的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: python pandas读写excel
- 下一篇: java类初始化顺序_「漫画」Java中