Java的多態為何可以由子類實例化父類?
public class TestPolymorphic {
public static void main(String[] args) {
System.out.println("年齡:"+c.age );
給出結論:當滿Java多態的三個條件時,可以發現c.eat()調用的實際上是子類的eat,但c.age調用的還是父類的age,而c.play()則不會通過編譯。
我們就從Father c = new Child()這句話切入
這句話首先會執行new Child(),在堆中分配一個對象。
當然在分配Child類的實例時,先要通過JVM的類加載器將Child類對應的class文件加載到JVM中,然后JVM根據class文件中的字節流產生一個表示class文件中類型信息的結構體
這個結構體的具體實現,不同的JVM會有不同的實現方式,但大致上都差不多,都要遵守JVM的規范。
這個表示class文件中類型信息的結構體大概由以下幾部分構成:
這些我說的也不夠准確,具體的大家可以看JVM相關的書籍如《深入理解Java虛擬機》,在這里就有個大概的概念就好。
之后,JVM會根據上面這個結構體生成一個叫做方法表的東西。這個方法表是實現java多態的一個關鍵。
方法表中包含的是實例方法(就是相對於靜態方法而言的,用對象訪問的那些方法)的直接引用,也就是說通過這個方法表就能夠訪問到該類的實例方法,
而且,這些實例方法不僅包括本類的方法,還包括其父類的實例方法,以及父類的父類的實例方法(就是一直到Object)。
上面提到過,方法表中不僅包括本類的方法,還包括父類的方法,方法表值這樣產生的,以Child類的方法表為例:
首先方法表中,會產生指向繼承自Object類的方法的引用,這些包括指向toString的和指向equals的,當然Object中還包括很多方法,這里就不寫了
然后方法表中產生指向繼承自Parent類的方法的引用,這包括eat,
這里再多說一句,表示類型信息的結構體中,的方法信息,只包含本類特有的,或者是重寫的方法信息,沒有父類的方法信息。
接下來是棧區,產生Father類型的引用,這個引用指向堆區中的Child類的實例。
但是我們都知道指針是不安全的,其中一個不安全因素就是指針可能訪問到沒有分配的內存空間,也就是說char *雖然限制了p指針訪問內存的方式,但是沒有限制能訪問內存的大小,這一點要完全靠程序員自己掌握。
但是在java的引用中Father不但指定了c以何種方式訪問內存,也規定了能夠訪問內存空間的大小。
我們看Father實例對象的大小是占兩行,但Child實例對象占三行(這里就是簡單量化一下)。
所以雖然c指向的是Child實例對象,但是前面有Father修飾它,它也只能訪問兩行的數據,也就是說c根本訪問不到Child類中的age!!!只能訪問到Father類的age,所以輸出40