深入理解java 多型與動態繫結

2021-08-13 06:02:10 字數 3793 閱讀 2122

首先,什麼是多型?

根據定義上來講,乙個物件變數可以指示多種實際型別的現象被稱為多型。

或者可以這樣說,乙個引用變數指向哪個類物件在程式設計時不能確定,要到要等到程式執行時才確認這個變數到底指向哪個類物件,呼叫的是哪個類的方法,這樣可以在不修改**的情況下,改變執行繫結的具體物件,讓程式可以選擇多個執行狀態,這就是多型性。

比如說乙個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 笛子類...

深入理解多型

能將每個函式都申明為虛函式,但是會影響效率,不建議這樣做,虛函式指標呼叫重寫函式是在程式執行時候進行的,因此需要一些定址操作才能真正呼叫函式,如果都設定成虛函式,效率會低很多 多型的實現效果 呼叫同樣的語句能表現不同的表現形式 多型實現的三個條件 有繼承,有虛函式重寫,有父類指標指向子類物件 多型的...