首先,什麼是多型?
根據定義上來講,乙個物件變數可以指示多種實際型別的現象被稱為多型。
或者可以這樣說,乙個引用變數指向哪個類物件在程式設計時不能確定,要到要等到程式執行時才確認這個變數到底指向哪個類物件,呼叫的是哪個類的方法,這樣可以在不修改**的情況下,改變執行繫結的具體物件,讓程式可以選擇多個執行狀態,這就是多型性。
比如說乙個manager類繼承自employee類,而乙個employee變數既可以引用乙個employee類物件,還可以引用manager類,這種行為就稱為多型。
下面看一段**:
manager boss = new manager();
employee staff = new employee[3];
staff[0] = boss;
在這段**中,staff[0]和boss引用同乙個物件。但是編譯器將staff[0]看成是employee物件,意味著,boss能呼叫子類的方法,但是staff[0]並不能呼叫manager類的獨有方法。
如:
boss.setbonus(); // ok
staff[0].setbonus();// error
這是因為staff[0]宣告的型別是employee類。
同樣的,也不能將超類的引用賦給子類變數。
這是因為乙個超類可以有很多個子類,如果manager類變數能引用employee類,那麼這個變數可以引用其他的employee子類,這樣的話呼叫其他子類獨有的方法時執行時就會報錯。
多型分為編譯時多型和執行時多型。其中編譯時多型是靜態的,主要是指方法的過載,它根據引數列表的不同來區分不同的方法,通過編輯之後會變成兩個不同的方法,在執行時不算多型。而執行時多型是動態的,它是通過***動態繫結***來實現的,也就是我們所說的多型性。
同樣的,要理解多型就要理解動態繫結,那麼,什麼是動態繫結?
在執行時能夠自動地選擇呼叫哪個方法的現象稱為動態繫結。
在這裡我們先來解釋一下方法呼叫的過程。
下面假設要呼叫x.f(args),x為類c的乙個物件。下面是呼叫過程的詳細描述:
<1>編譯器檢視物件宣告型別和方法名。假設呼叫x.f(param),且x為類c的乙個物件。需要注意:這裡有可能會存在多個名字為f,但是引數型別不一樣的方法,比如可能存在f (int),f(long)。編譯器將會一一枚舉所有c類中名為f的方法,和其超類中訪問屬性為public且名為f的方法(超類的私有方法不可訪問)。
此時,編譯器已獲得所有可能被呼叫的候選方法。
<2>接下來,編譯器將檢視呼叫方法時提供的引數型別,如果在所有方法名為f的方法中存在乙個與提供引數型別完全匹配,就選擇這個方法。這個過程被稱為過載解析。比如呼叫f(「hello,world!」),那麼,編譯器將選擇f(string)而不是其他,不過由於允許型別轉換(int可以轉換成double,manager可以轉換成employee,等等),這個過程可能會很複雜。如果編譯器沒有找到與引數型別匹配的方法,或者經過型別轉換後發現有多個方法與之匹配,就會報告乙個錯誤。
此時,編譯器已獲得需要呼叫的方法名字和引數型別。
<3>如果這個方法是private,static,final方法或者是構造器,那麼編譯器將可以準確的知道應該呼叫哪個方法,我們將這種呼叫方式稱為靜態繫結。與之對應,呼叫的方法依賴於變數的實際型別,並且在執行時實現動態繫結。
<4>當程式執行時,並且使用動態繫結呼叫方法時,虛擬機器一定呼叫與x所引用物件的實際型別最合適的那個類的方法,如x的實際型別是d,d繼承自c類。那麼呼叫f時,先檢查d類方法中的f(string),沒有就再去d的超類中找f(string),以此類推。
最後,由於每次呼叫方法都要進行搜尋,時間開銷非常大。因此,虛擬機器預先為每個類建立了乙個方法表,其中列出了所有方法的簽名(即方法的名字和引數型別,不包括返回型別)和實際呼叫的方法。這樣的話,在真正呼叫方法時,虛擬機器僅查詢這個表就行了。在前面的例子中,虛擬機器搜尋d類的方法,以便尋找與呼叫f(string)相匹配的方法,這個方法既可能是d.f(string),也有可能是x.f(string).這裡x是d的超類。這裡需要提醒一下,如果呼叫super.f(param),編譯器將對隱式引數的超類的方法表進行搜尋。
下面看乙個例項,這是有關多型的經典例子,摘自:
public class a
public string show(a obj)
}public class b extends a
public string show(a obj)
}public class c extends b
public class d extends b
public class test
}
執行結果:
① a and a
② a and a
③ a and d
④ b and a
⑤ b and a
⑥ a and d
⑦ b and b
⑧ b and b
⑨ a and d
①②③比較好理解,一般不會出錯。④⑤就有點糊塗了,為什麼輸出的不是"b and b」呢?!!
先讓我們來看一下這個部落格上的一句話:
當超類物件引用變數引用子類物件時,被引用物件的型別而不是引用變數的型別決定了呼叫誰的成員方法,但是這個被呼叫的方法必須是在超類中定義過的,也就是說被子類覆蓋的方法。現在列出方法表,(但是如果強制把超類轉換成子類的話,就可以呼叫子類中新新增而超類沒有的方法了。)
實際上這裡涉及方法呼叫的優先問題,優先順序由高到低依次為:this.show(o),super.show(o),this.show((super)o),super.show((super)o)。
a類:show(d obj) -> a.show(d obj)
show(a obj) -> a.show(a obj)
b類:show(b obj) ->b.show(b obj)
show(a obj) ->a.show(a obj)
上面列出的方法不完整,還有object的方法就不列出了。
<1>在5中,a2宣告的為a類,所以編譯器將會提取a2可能被呼叫的候選方法,即show(d obj)和show(a obj)。
<3>這裡在a中找到了show(a obj),根據方法呼叫過程分析中的<4>,即當程式執行時,並且使用動態繫結呼叫方法時。虛擬機器一定呼叫與x所引用物件的實際型別最合適的那個類的方法,所以由於a2是b類的乙個引用且b類重寫了show(a obj),因此最終會呼叫子類b類的show(a obj)方法,結果也就是b and a。
當然對<3>的解釋也可以引用部落格上的:
當超類物件引用變數引用子類物件時,被引用物件的型別而不是引用變數的型別決定了呼叫誰的成員方法,但是這個被呼叫的方法必須是在超類中定義過的,也就是說被子類覆蓋的方法。來理解。
動態繫結的乙個非常重要的特性:無需對現存的**修改,就可以對程式進行擴充套件。就假設增加乙個新類executive,並且變數e有可能引用這個類的物件,不需要對包含呼叫方法進行重新編譯。虛擬機會自動呼叫這個類的這個方法。
以上,是我做的讀書筆記,希望能對你理解多型和動態繫結有幫助。
深入理解Java多型機制
目錄 1,多型的概念?2,存在的條件?3,案列解析?4,應用場景?1,多型的概念 父類引用指向子類物件,通俗點就是,在編譯時不繫結是什麼方法,根據你傳進來的值,是什麼就會執行什麼。2.存在條件 第一,要有繼承 第二,要有方法的重寫 第三,父類引用指向子類物件 3,案列解析 好好體會以下這個案例,通過...
java多型深入理解 一
很多人都知道物件導向的三大特性 繼承 封裝 多型,可是真正理解好這三特性並不是一件簡單的事.本週我打算對多型進行研究並寫下我的小例子 樂器類 class musicinstruments public void instruments musicinstruments instruments 笛子類...
深入理解多型
能將每個函式都申明為虛函式,但是會影響效率,不建議這樣做,虛函式指標呼叫重寫函式是在程式執行時候進行的,因此需要一些定址操作才能真正呼叫函式,如果都設定成虛函式,效率會低很多 多型的實現效果 呼叫同樣的語句能表現不同的表現形式 多型實現的三個條件 有繼承,有虛函式重寫,有父類指標指向子類物件 多型的...