一、靜態(tài)綁定和動(dòng)態(tài)綁定的區(qū)別
在Java中,當(dāng)你調(diào)用一個(gè)方法時(shí),可能會(huì)在編譯時(shí)期(compile time)解析(resolve),也可能實(shí)在運(yùn)行時(shí)期(runtime)解析,這全取決于到底是一個(gè)靜態(tài)方法(static method)還是一個(gè)虛方法(virtual method)。如果是在編譯時(shí)期解析,那么就稱之為靜態(tài)綁定(static binding),如果方法的調(diào)用是在運(yùn)行時(shí)期解析,那就是動(dòng)態(tài)綁定(dynamic binding)或者延遲綁定(late binding)。Java是一門面向?qū)ο蟮木幊陶Z(yǔ)言,優(yōu)勢(shì)就在于支持多態(tài)(Polymorphism)。多態(tài)使得父類型的引用變量可以引用子類型的對(duì)象。如果調(diào)用子類型對(duì)象的一個(gè)虛方法(非private,final or static),編譯器將無(wú)法找到真正需要調(diào)用的方法,因?yàn)樗赡苁嵌x在父類型中的方法,也可能是在子類型中被重寫(override)的方法,這種情形,只能在運(yùn)行時(shí)進(jìn)行解析,因?yàn)橹挥性谶\(yùn)行時(shí)期,才能明確具體的對(duì)象到底是什么。這也是我們俗稱的運(yùn)行時(shí)或動(dòng)態(tài)綁定(runtime or dynamic binding)。另一方面,private static和final方法將在編譯時(shí)解析,因?yàn)榫幾g器知道它們不能被重寫,所有可能的方法都被定義在了一個(gè)類中,這些方法只能通過(guò)此類的引用變量進(jìn)行調(diào)用。這叫做靜態(tài)綁定或編譯時(shí)綁定(static or compile time binding)。所有的private,static和final方法都通過(guò)靜態(tài)綁定進(jìn)行解析。這兩個(gè)概念的關(guān)系,與“方法重載”(overloading,靜態(tài)綁定)和“方法重寫”(overriding,動(dòng)態(tài)綁定)類似。動(dòng)態(tài)綁定只有在重寫可能存在時(shí)才會(huì)用到,而重載的方法在編譯時(shí)期即可確定(這是因?yàn)樗鼈兛偸嵌x在同一個(gè)類里面)
總而言之,其區(qū)別如下:
①靜態(tài)綁定在編譯時(shí)期,動(dòng)態(tài)綁定在運(yùn)行時(shí)期。
②靜態(tài)綁定只用到類型信息,方法的解析根據(jù)引用變量的類型決定,而動(dòng)態(tài)綁定則根據(jù)實(shí)際引用的的對(duì)象決定
③在java中,private static 和 final 方法都是靜態(tài)綁定,只有虛方法才是動(dòng)態(tài)綁定
④多態(tài)是通過(guò)動(dòng)態(tài)綁定實(shí)現(xiàn)的。
二、動(dòng)態(tài)綁定是如何實(shí)現(xiàn)的?
一個(gè)對(duì)象的多態(tài)方法的地址將被存儲(chǔ)在該對(duì)象的方法表(method table)里面。在運(yùn)行時(shí)期,調(diào)用多態(tài)方法的時(shí)候,JVM會(huì)在此表中搜索方法的名字,從而獲取方法的地址。方法表里包含方法的名字和對(duì)應(yīng)的地址(注意,這個(gè)地址是動(dòng)態(tài)綁定的)。這個(gè)方法表對(duì)所有屬于這個(gè)類的對(duì)象而言,都是一樣的,所以它會(huì)存儲(chǔ)在Class對(duì)象中(這里對(duì)象類型以Integer為例)(在其他的語(yǔ)言中,這樣的表又叫做vtables,虛函數(shù)表)。需要說(shuō)明的是,java語(yǔ)言中,如果沒(méi)有添加任何關(guān)鍵字,則方法默認(rèn)就是虛方法,任何子類都可以重寫它。
方法表并不屬于語(yǔ)言的一部分,但是會(huì)有很多種不同的實(shí)現(xiàn)(不同的JVM提供商可以自由選擇實(shí)現(xiàn)的細(xì)節(jié),只要結(jié)果保證一致就ok)。其中,Sun公司的JVM實(shí)現(xiàn),則選擇了將方法表入口放在對(duì)象的常量池(constant pool)里,你可以使用命令java -verbose foo來(lái)查看。(所有的屬于同一個(gè)類型的對(duì)象都將擁有同一個(gè)方法表,JVM也可以將其放在別的地方)
下面,將通過(guò)一個(gè)圖表實(shí)例來(lái)展示,對(duì)于某些類(這里以Integer為例)而言,是如何一步步構(gòu)建方法表的。初始時(shí),表都是空的。運(yùn)行時(shí),方法表將從最遠(yuǎn)的祖先類開始,逐步加入這些多態(tài)方法。通常,這個(gè)最遠(yuǎn)的祖先是Object類。
方法名 地址 注釋
Object.toString 111 Object.toString method address
... ... 10個(gè)其他的方法
接下來(lái),這個(gè)表中將加入第二遠(yuǎn)的祖先類的多態(tài)方法,如果已經(jīng)存在,就修改其地址值。此例中,第二遠(yuǎn)的類是Number類。如果你查看了javadoc,你就會(huì)發(fā)現(xiàn)Number類并沒(méi)有重寫任何方法,只是額外多了六個(gè)方法,因而,將這六個(gè)多的方法加入表中。此時(shí),toString項(xiàng)并沒(méi)有被改變,方法表如下:
方法名 地址 注釋
Object.toString 111 Number.toString method address
Number.intValue 222 Number.intValue method address
... ... 15個(gè)其他的方法
這個(gè)過(guò)程一直持續(xù)下去,直到所有的父類的多態(tài)方法都被合并進(jìn)這個(gè)表里。最后,方法表會(huì)被Integer類的多態(tài)方法所更新,此時(shí),toString方法會(huì)被重寫:
方法名 地址 注釋
Object.toString 333 Integer.toString method address
Number.intValue 444 interger.intValue method address
Integer.parseInt 555 Number.longValue method address
... ... 其他的一些方法
方法表中的方法名這一項(xiàng),只包含最初始的類名,所謂重寫,只是修改了地址欄下的值,不會(huì)改變方法名的值。所以,如果用javap指令查看多態(tài)方法名,只會(huì)顯示Object.toString,而不是toString或者Integer.toString。
需要說(shuō)明的是,此處,假如有Number num = new Integer(10),即便方法表里面有了Integer.parseInt方法,我們?nèi)圆荒芡ㄟ^(guò)num來(lái)調(diào)用parseInt。也就是說(shuō),num變量引用了Integer對(duì)象,并且與上述方法表關(guān)聯(lián),但在編譯時(shí)期時(shí),編譯器會(huì)根據(jù)語(yǔ)法規(guī)則,實(shí)行訪問(wèn)控制,num不能調(diào)用和訪問(wèn)獨(dú)屬于Integer的類方法,只能訪問(wèn)自己擁有訪問(wèn)權(quán)限的類方法,而這些方法中的某些方法,在運(yùn)行過(guò)程中,名字未變,映射地址卻發(fā)生了變化,因而調(diào)用的是所引用的子類Integer的實(shí)現(xiàn)。這點(diǎn)要弄清楚!