之前一直知道多態是什麼東西,平時敲代碼也經常用到多態,但一直沒有真正了解多態底層的運行機制到底是怎麼樣的,這兩天才研究明白點,特地寫下來,跟各位同學一起進步,同時也希望各位大神指導和指正。
多態的概念:同一操作作用於不同對象,可以有不同的解釋,有不同的執行結果,這就是多態,簡單來說就是:父類的引用指向子類對象。下面先看一段代碼
1 package polymorphism; 2 3 class Dance { 4 public void play(){ 5 System.out.println("Dance.play"); 6 } 7 public void play(int i){ 8 System.out.println("Dance.play" + i); 9 } 10 } 11 12 class Latin extends Dance { 13 public void play(){ 14 System.out.println("Latin.play"); 15 } 16 public void play(char c){ 17 System.out.println("Latin.play" + c); 18 } 19 } 20 class Jazz extends Dance { 21 public void play(){ 22 System.out.println("Jazz.play"); 23 } 24 public void play(double d){ 25 System.out.println("Jazz.play" + d); 26 } 27 } 28 public class Test { 29 public void perform(Dance dance){ 30 dance.play(); 31 } 32 public static void main(String[] args){ 33 new Test().perform(new Latin()); // Upcasting 34 } 35 }View Code
執行結果:Latin.play。這個時候你可能會發現perform()方法裡面並沒有類似“if 參數類型 = Dance/Latin”這樣的判斷,其實這就是多態的特性,它消除了類型之間的耦合關系,令我們可以把一個對象不當做它所屬的特定類型來對待,而是當做其基類的類型來對待。因為上面的Test代碼完全可以這麼寫:
1 public class Test { 2 public void perform(Latin dance){ 3 dance.play(); 4 } 5 public void perform(Jazz dance){ 6 dance.play(); 7 } 8 public static void main(String[] args){ 9 new Test().perform(new Latin()); // Upcasting 10 } 11 }View Code
但是這樣你會發現,如果增加更多新的類似perform()或者自Dance導出的新類,就會增加大量的工作,而通過比較就會知道第一處代碼會占優勢,這正是多態的優點所在,它改善了代碼的組織結構和可讀性,同時保證了可擴展性。
那麼到底JVM是怎麼指向Latin類中play()的呢?為了解決這個問題,JAVA使用了後期綁定的概念。當向對象發送消息時,在編譯階段,編譯器只保證被調用方法的存在,並對調用參數和返回類型進行檢查,但是並不知道將被執行的確切代碼,被調用的代碼直到運行時才能確定。拿上面代碼為例,JAVA在執行後期綁定時,JVM會從方法區中的Latin方法表中取到Latin對象的直接地址,這就是真正執行結果為什麼是Latin.play的原因,在解釋方法表之前,我們先了解一下綁定的概念。
將一個方法調用同一個方法主體關聯起來被稱作綁定,JAVA中分為前期綁定和後期綁定(動態綁定或運行時綁定),在程序執行之前進行綁定(由編譯器和連接程序實現)叫做前期綁定,因為在編譯階段被調用方法的直接地址就已經存儲在方法所屬類的常量池中了,程序執行時直接調用,具體解釋請看最後參考資料地址。後期綁定含義就是在程序運行時根據對象的類型進行綁定,想實現後期綁定,就必須具有某種機制,以便在運行時能判斷對象的類型,從而找到對應的方法,簡言之就是必須在對象中安置某種“類型信”,JAVA中除了static方法、final方法(private方法屬於)之外,其他的方法都是後期綁定。後期綁定會涉及到JVM管理下的一個重要的數據結構——方法表,方法表以數組的形式記錄當前類及其所有父類的可見方法字節碼在內存中的直接地址。
動態綁定具體的調用過程為:
1.首先會找到被調用方法所屬類的全限定名
2.在此類的方法表中尋找被調用方法,如果找到,會將方法表中此方法的索引項記錄到常量池中(這個過程叫常量池解析),如果沒有,編譯失敗。
3.根據具體實例化的對象找到方法區中此對象的方法表,再找到方法表中的被調用方法,最後通過直接地址找到字節碼所在的內存空間。
最後說明,域和靜態方法都是不具有多態性的,任何的域訪問操作都將由編譯器解析,因此不是多態的。靜態方法是跟類,而並非單個對象相關聯的。對動態綁定還有不明白的請看資料鏈接,個人感覺分析的很到位。