Java的多態機制原理


一、靜態綁定和動態綁定的區別

在Java中,當你調用一個方法時,可能會在編譯時期(compile time)解析(resolve),也可能實在運行時期(runtime) 解析,這全取決於到底是一個靜態方法(static method )還是一個虛方法(virtual method)。如果是在編譯時期解析,那么就稱之為靜態綁定(staticbinding)[換句話說靜態方法就是在編譯期解析的],如果方法的調用是在運行時期解析,那就是動態綁定(dynamic binding)或者延遲綁定(late binding)。

Java是一門面向對象的編程語言,優勢就在於支持多態( Polymorphism)。多態使得父類型的引用變量可以引用子類型的對象。如果調用子類型對象的一個虛方法(非private,final or static),編譯器將無法在子類中找到真正需要調用的方法,因為它可能是定義在父類型中的方法(從父類繼承過來) ,也可能是在子類型中被重寫(override) 的方法,這種情形,只能在運行時進行解析(說白了就是不能在編譯期間確定要調用的方法在哪兒),因為只有在運行時期,才能明確具體的對象.到底是什么。這也是我們俗稱的運行時或動態綁定( runtime or dynamic binding)。

另一方面,privatestatic和final方法將在編譯時解析,因為編譯器知道它們不能被重寫,所有可能的方法都被定義在了一個類中,這些方法只能通過此類的引用變量進行調用。這叫做靜態綁定或編譯時綁定(static or compile time binding)。動態綁定只有在重寫可能存在時才會用到,而重載的方法在編譯時期即可確定( 因為重載的方法是在一個類中出現的)

 

總而言之,其區別如下:

①靜態綁定在編譯時期,動態綁定在運行時期。

②靜態綁定只用到類型信息,方法的解析根據引用變量的類型決定,而動態綁定則根據實際引用的的對象決定(對象只有在程序運行的時候才會在內存中出現)

③在java中,private static和final 方法都是靜態綁定,只有虛方法才是動態綁定

④多態是通過動態綁定實現的,我們上節課講了多態的概念,那么今天你要知道之所以能實現多態就是Java提供了動態綁定機制。

 

二、動態綁定是如何實現的?

一個對象的多態方法的地址將被存儲在該對象的方法表(methodtable)里面(每個對象都會有一個方法表)。在運行時期,調用多態方法的時候,JVM會在此表中搜索方法的名字,從而獲取方法的地址。方法表里包含方法的名字和對應的地址(注意,這個地址是動態綁定的)。這個方法表對所有屬於這個類的對象而言,都是一樣的,所以它會存儲在Class ( 我們會在反射的時候講解)對象中(這里對象類型以Integer為例) ( 在其他的語言中,這樣的表又叫做vtables,虛函數表)。需要說明的是,java語言中,如果沒有添加任何關鍵字,則方法默認就是虛方法,任何子類都可以重寫它。

方法表並不屬於語言的一-部分,但是會有很多種不同的實現(不同的JVM提供商可以自由選擇實現的細節,只要結果保證一致就ok) 。其中,Sun公司的JVM實現,則選擇了將方法表入口放在對象的常量池( constant pool)里(這里的對象池是class文件中的一個區域)

下面,將通過一個圖表實例來展示,對於某些類(這里以Integer為例)而言,是如何一步步構建方法表的。初始時,表都是空的。運行時,方法表將從最遠的祖先類開始,逐步加入這些多態方法。通常,這個最遠的祖先是Object類。

 

接下來,這個表中將加入第二遠的祖先類的多態方法,如果已經存在,就修改其地址值(這就是子類對父類方法的重寫原因)。此例中,第二遠的類是Number類。如果你查看了javadoc,你就會發現Number類並沒有重寫任何方法,只是額外多了六個方法,因而,將這六個多的方法加入表中。此時,toString 項並沒有被改變,方法表如下:

 

 

這個過程一-直持續下去,直到所有的父類的多態方法都被合並進這個表里。最后,方法表會被Integer類的多態方法所更新,此時,toString 方法會被重寫:

 

 

方法表中的方法名這一項,只包含最初始的類名,所謂重寫,只是修改了地址欄下的值,不會改變方法名的值。需要說明的是,此處,假如有Number num = new Integer(10),即便方法表里面有了Integer. parseInt方法,我們仍不能通過num來調用parseInt。也就是說,num變量引用了Integer對象,並且與上述方法表關聯,但在編譯時期時,編譯器會根據語法規則,實行訪問控制,num不能調用和訪問獨屬於Integer的類方法,只能訪問自己擁有訪問權限的類方法,而這些方法中的某些方法,在運行過程中,名字未變,映射地址卻發生了變化,因而調用的是所引用的子類Integer的實現。這點要弄清楚!

父類類型的引用不能調用子類新擴充的方法!

有完整的Java初級,高級對應的學習路線和資料!專注於java開發。分享java基礎、原理性知識、JavaWeb實戰、spring全家桶、設計模式、分布式及面試資料、開源項目,助力開發者成長!


歡迎關注微信公眾號:碼邦主

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM