動態綁定
動態綁定
動態綁定是指在執行期間(非編譯期)判斷所引用對象的實際類型,根據其實際的類型調用其相應的方法。程序運行過程中,把函數(或過程)調用與響應調用所需要的代碼相結合的過程稱為動態綁定。
把一個方法與其所在的類/對象關聯起來叫做方法的綁定。綁定分為靜態綁定(前期綁定)和動態綁定(後期綁定)。靜態綁定(前期綁定)是指在程序運行前就已經知道方法是屬於那個類的,在編譯的時候就可以連接到類的中,定位到這個方法。動態綁定(後期綁定)是指在程序運行過程中,根據具體的實例對象才能具體確定是哪個方法。
靜態綁定發生於數據結構和數據結構間,程序執行之前。靜態綁定發生於編譯期,因此不能利用任何運行期的信息。它針對函數調用與函數的主體,或變數與內存中的區塊。動態綁定則針對運行期產生的訪問請求,只用到運行期的可用信息。在面向對象的代碼中,動態綁定意味著決定哪個方法被調用或哪個屬性被訪問,將基於這個類本身而不基於訪問範圍。
C++中,通過基類的引用或指針調用虛函數時,發生動態綁定。引用(或指針)既可以指向基類對象也可以指向派生類對象,這一事實是動態綁定的關鍵。用引用(或指針)調用的虛函數在運行時確定,被調用的函數是引用(或指針)所指對象的實際類型所定義的。
C++中動態綁定是通過虛函數實現的。而虛函數是通過一張虛函數表(virtualtable)實現的。這個表中記錄了虛函數的地址,解決繼承、覆蓋的問題,保證動態綁定時能夠根據對象的實際類型調用正確的函數。
在C++的標準規格說明書中說到,編譯器必需要保證虛函數表的指針存在於對象實例中最前面的位置(這是為了保證正確取到虛函數的偏移量)。這意味著我們通過對象實例的地址得到這張虛函數表,然後就可以遍歷其中函數指針,並調用相應的函數。
1.動態綁定在函數調用時需要在虛函數表中查找,所以性能比靜態函數調用稍低。
2.通過基類類型的指針訪問派生類自己的虛函數將發生錯誤。
動態綁定(後期綁定)是指:在程序運行過程中,根據具體的實例對象才能具體確定是哪個方法。
動態綁定是多態性得以實現的重要因素,它通過方法表來實現:每個類被載入到虛擬機時,在方法區保存元數據,其中,包括一個叫做方法表(methodtable)的東西,表中記錄了這個類定義的方法的指針,每個表項指向一個具體的方法代碼。如果這個類重寫了父類中的某個方法,則對應表項指向新的代碼實現處。從父類繼承來的方法位於子類定義的方法的前面。
我們假設Fatherft=newSon();ft.say();Son繼承自Father,重寫了say()。
我們知道,向上轉型時,用父類引用執行子類對象,並可以用父類引用調用子類中重寫了的同名方法。但是不能調用子類中新增的方法。
在代碼的編譯階段,編譯器通過聲明對象的類型(即引用本身的類型)在方法區中該類型的方法表中查找匹配的方法(最佳匹配法:參數類型最接近的被調用),如果有則編譯通過。(這裡是根據聲明的對象類型來查找的,所以此處是查找Father類的方法表,而Father類方法表中是沒有子類新增的方法的,所以不能調用。)
編譯階段是確保方法的存在性,保證程序能順利、安全運行。
ft.say()調用的是Son中的say(),這裡就是動態綁定機制的真正體現。
編譯階段在聲明對象類型的方法表中查找方法,只是為了安全地通過編譯(也為了檢驗方法是否是存在的)。而在實際運行這條語句時,在執行Fatherft=newSon();這一句時創建了一個Son實例對象,然後在ft.say()調用方法時,JVM會把剛才的son對象壓入操作數棧,用它來進行調用。而用實例對象進行方法調用的過程就是動態綁定:根據實例對象所屬的類型去查找它的方法表,找到匹配的方法進行調用。我們知道,子類中如果重寫了父類的方法,則方法表中同名表項會指向子類的方法代碼;若無重寫,則按照父類中的方法表順序保存在子類方法表中。故此:動態綁定根據對象的類型的方法表查找方法是一定會匹配(因為編譯時在父類方法表中以及查找並匹配成功了,說明方法是存在的。這也解釋了為何向上轉型時父類引用不能調用子類新增的方法:在父類方法表中必須先對這個方法的存在性進行檢驗,如果在運行時才檢驗就容易出危險——可能子類中也沒有這個方法)。
程序在JVM運行過程中,會把類的類型信息、static屬性和方法、final常量等元數據載入到方法區,這些在類被載入時就已經知道,不需對象的創建就能訪問的,就是靜態綁定的內容;需要等對象創建出來,使用時根據堆中的實例對象的類型才進行取用的就是動態綁定的內容。