Java多態和動態綁定是如何實現的


最近深入學習java,看到了動態綁定和多態這一章節,但遺憾的是,大部分的相關文章都停留於表面文字的描述。不得已,最后google了幾篇英文文章,在此總結下這個問題。

 

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

在Java中,當你調用一個方法時,可能會在編譯時期(compile time)解析(resolve),也可能實在運行時期(runtime)解析,這全取決於到底是一個靜態方法(static method)還是一個虛方法(virtual method)。如果是在編譯時期解析,那么就稱之為靜態綁定(static binding),如果方法的調用是在運行時期解析,那就是動態綁定(dynamic binding)或者延遲綁定(late binding)。Java是一門面向對象的編程語言,優勢就在於支持多態(Polymorphism)。多態使得父類型的引用變量可以引用子類型的對象。如果調用子類型對象的一個虛方法(非private,final or static),編譯器將無法找到真正需要調用的方法,因為它可能是定義在父類型中的方法,也可能是在子類型中被重寫(override)的方法,這種情形,只能在運行時進行解析,因為只有在運行時期,才能明確具體的對象到底是什么。這也是我們俗稱的運行時或動態綁定(runtime or dynamic binding)。另一方面,private static和final方法將在編譯時解析,因為編譯器知道它們不能被重寫,所有可能的方法都被定義在了一個類中,這些方法只能通過此類的引用變量進行調用。這叫做靜態綁定或編譯時綁定(static or compile time binding)。所有的private,static和final方法都通過靜態綁定進行解析。這兩個概念的關系,與“方法重載”(overloading,靜態綁定)和“方法重寫”(overriding,動態綁定)類似。動態綁定只有在重寫可能存在時才會用到,而重載的方法在編譯時期即可確定(這是因為它們總是定義在同一個類里面)

總而言之,其區別如下:

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

②靜態綁定只用到類型信息,方法的解析根據引用變量的類型決定,而動態綁定則根據實際引用的的對象決定

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

④多態是通過動態綁定實現的。

 

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

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

方法表並不屬於語言的一部分,但是會有很多種不同的實現(不同的JVM提供商可以自由選擇實現的細節,只要結果保證一致就ok)。其中,Sun公司的JVM實現,則選擇了將方法表入口放在對象的常量池(constant pool)里,你可以使用命令java -verbose foo來查看。(所有的屬於同一個類型的對象都將擁有同一個方法表,JVM也可以將其放在別的地方)

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

           方法名      地址                           注釋
     Object.toString             111          Object.toString method address    
              ...       ...                 10個其他的方法

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

方法名 地址 注釋
Object.toString 111 Number.toString method address
Number.intValue 222 Number.intValue method address
... ... 15個其他的方法

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

方法名 地址 注釋
Object.toString 333 Integer.toString method address
Number.intValue 444 interger.intValue method address
Integer.parseInt 555 Number.longValue method address
... ... 其他的一些方法

方法表中的方法名這一項,只包含最初始的類名,所謂重寫,只是修改了地址欄下的值,不會改變方法名的值。所以,如果用javap指令查看多態方法名,只會顯示Object.toString,而不是toString或者Integer.toString。

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


免責聲明!

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



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