天天看點

從JVM角度看Java多态

Java多态的三個必要條件:

1、 繼承

2、 子類重寫父類方法

3、 父類引用指向子類對象

然後看一個例子

輸出結果為:

從JVM角度看Java多态

給出結論:當滿Java多态的三個條件時,可以發現c.eat()調用的實際上是子類的eat,但c.age調用的還是父類的age,而c.play()則不會通過編譯。

下面從JVM的角度解釋上面這種現象

我們就從Father c = new Child()這句話切入

這句話首先會執行new Child(),在堆中配置設定一個對象。

當然在配置設定Child類的執行個體時,先要通過JVM的類加載器将Child類對應的class檔案加載到JVM中,然後JVM根據class檔案中的位元組流産生一個表示class檔案中類型資訊的結構體

這個結構體的具體實作,不同的JVM會有不同的實作方式,但大緻上都差不多,都要遵守JVM的規範。

這個表示class檔案中類型資訊的結構體大概由以下幾部分構成:

1、 常量池

2、 類變量(靜态變量)

3、 字段資訊

4、 方法資訊

5、 類的父類資訊

6、 類的通路權限資訊等

這些我說的也不夠準确,具體的大家可以看JVM相關的書籍如《深入了解Java虛拟機》,在這裡就有個大概的概念就好。

之後,JVM會根據上面這個結構體生成一個叫做方法表的東西。這個方法表是實作java多态的一個關鍵。

方法表中包含的是執行個體方法(就是相對于靜态方法而言的,用對象通路的那些方法)的直接引用,也就是說通過這個方法表就能夠通路到該類的執行個體方法,

而且,這些執行個體方法不僅包括本類的方法,還包括其父類的執行個體方法,以及父類的父類的執行個體方法(就是一直到Object)。

而且,這些方法中不包含私有方法(因為私有方法不能繼承)

方法表中的這些直接應用會指向到JVM中表示類型資訊的那個結構體(就是上面那個結構體)的相應的方法資訊(就是上面結構體中4的某個位置),當然這隻是本類的方法,表中還有父類的方法,相應地指向父類類型資訊結構體的具體位置。

可能表達的不夠清晰,下面畫個圖表示。

從JVM角度看Java多态

上面提到過,方法表中不僅包括本類的方法,還包括父類的方法,方法表值這樣産生的,以Child類的方法表為例:

首先方法表中,會産生指向繼承自Object類的方法的引用,這些包括指向toString的和指向equals的,當然Object中還包括很多方法,這裡就不寫了

然後方法表中産生指向繼承自Parent類的方法的引用,這包括eat,

最後産生指向本類的方法的引用。

這裡需要注意的一點是,當Child類的方法表産生指向Parent類中的方法的引用時,會有一個指向eat方法的引用,最後産生指向本類的方法的引用時,也有一個指向eat的引用,這時候,新的資料會覆寫原有的資料,也就是說原來指向Parent.eat的那個引用會被替換成指向Child.eat的引用(占據原來表中的位置)。是以我們看到在Child類的方法表中指向的是Child.eat而Parent類的方法表中指向的是Parent.eat。子類的方法表中就沒有指向Parent.eat的引用了。

而且還要注意一個特點就是,Parent和Child的方法表中,指向eat的引用在表中的偏移量是一樣的,都是第三個位置。(這是因為子類eat方法覆寫掉了父類eat方法,占據了原來父類eat方法的引用在表中的位置)

這裡再多說一句,表示類型資訊的結構體中,的方法資訊,隻包含本類特有的,或者是重寫的方法資訊,沒有父類的方法資訊。

了解了方法區的結構後,我們來看堆中對象的結構

從JVM角度看Java多态

接下來是棧區,産生Father類型的引用,這個引用指向堆區中的Child類的執行個體。

這裡需要解釋一下Father c的含義,我們知道c表示一個引用,這個引用指向堆中的Child類的執行個體,說白了就是一個位址,這個位址指向堆中的Child的類的執行個體,但是我們不要忘記前面還有一個Father修飾這個c

我們都知道在c中有void類型的指針,而給指針前面限定一個類型就限制了指針通路記憶體的方式,比如char * p就表示p隻能一個位元組一個位元組地通路記憶體,但是int *p中p就必須四個位元組四個位元組地通路記憶體。

但是我們都知道指針是不安全的,其中一個不安全因素就是指針可能通路到沒有配置設定的記憶體空間,也就是說char *雖然限制了p指針通路記憶體的方式,但是沒有限制能通路記憶體的大小,這一點要完全靠程式員自己掌握。

但是在java的引用中Father不但指定了c以何種方式通路記憶體,也規定了能夠通路記憶體空間的大小。

我們看Father執行個體對象的大小是占兩行,但Child執行個體對象占三行(這裡就是簡單量化一下)。

是以雖然c指向的是Child執行個體對象,但是前面有Father修飾它,它也隻能通路兩行的資料,也就是說c根本通路不到Child類中的age!!!隻能通路到Father類的age,是以輸出40

而且我們注意兩個類的方法表:

從JVM角度看Java多态

我們看到Parent的方法表占三行,Child的方法表占4行,c雖然指向了Child類的執行個體對象,而對象中也有指針指向Child類的方法表,但是由于c受到了Father的修飾,通過c也隻能通路到Child方法表中前3行的内容!!!!

然而前面說過,在方法表的形成過程中,子類重寫的方法會覆寫掉表中原來的資料,也就是Child類的方法表的第三行是指向Child.eat的引用,而不是指向Parent.eat(因為方法表産生了覆寫),是以c通路到的是Child.eat。也就是子類的方法!!!這種情況下,c是沒有辦法直接通路到父類的eat方法的。

本文轉自帥氣的頭頭部落格51CTO部落格,原文連結http://blog.51cto.com/12902932/1925688如需轉載請自行聯系原作者

sshpp