Java多態的底層原理

作爲一門面嚮對象語言,Java 擁有封裝、繼承、多態三大特性。多態就是允許不同類的對象對同一消息做出響應。基於多態,可以消除一些類型耦合關係,實現可替換、可擴充。Java 中使用多態特性的方法主要有,實現一個接口,實現抽象類的一個方法,覆蓋父類的一個方法。

多態的底層實現是動態綁定,即在運行時才把方法調用與方法實現關聯起來。動態綁定涉及很多 JVM 的細節,全部寫清楚需要很大篇幅,因此本文僅簡單描述,後續會有其他文章就其中的細節一一分享。

靜態綁定與動態綁定

JVM 的方法調用指令有五個,分別是:

invokestatic:調用靜態方法;

invokespecial:調用實例構造器

方法、私有方法和父類方法;

invokevirtual:調用虛方法;

invokeinterface:調用接口方法,運行時確定具體實現;

invokedynamic:運行時動態解析所引用的方法,然後再執行,用於支持動態類型語言。

小編是一個有着5年工作經驗的java程序員,對於java,自己有做資料的整合,一個完整學習java的路線,學習資料和工具,相信這裏有很多學習java的小夥伴,我創立了一個2000人學習扣羣,479121291。每晚都有java的直播課程。無論是初級還是進階的小夥伴小編我都歡迎!

其中,invokestatic 和 invokespecial 用於靜態綁定,invokevirtual 和 invokeinterface 用於動態綁定。可以看出,動態綁定主要應用於虛方法和接口方法。

靜態綁定在編譯期就已經確定,這是因爲靜態方法、構造器方法、私有方法和父類方法可以唯一確定。這些方法的符號引用在類加載的解析階段就會解析成直接引用。因此這些方法也被稱爲非虛方法,與之相對的便是虛方法。

虛方法的方法調用與方法實現的關聯(也就是分派)有兩種,一種是在編譯期確定,被稱爲靜態分派,比如方法的重載;一種是在運行時確定,被稱爲動態分派,比如方法的覆蓋。對象方法基本上都是虛方法。

這裏需要特別說明的是,final 方法由於不能被覆蓋,可以唯一確定,因此 Java 語言規範規定 final 方法屬於非虛方法,但仍然使用 invokevirtual 指令調用。靜態綁定、動態綁定的概念和虛方法、非虛方法的概念是兩個不同的概念。

多態的實現

在前文《Java線程知識拾遺》中提到過,虛擬機棧中會存放當前方法調用的棧幀,在棧幀中,存儲着局部變量表、操作棧、動態連接 、返回地址和其他附加信息。多態的實現過程,就是方法調用動態分派的過程,通過棧幀的信息去找到被調用方法的具體實現,然後使用這個具體實現的直接引用完成方法調用。

以 invokevirtual 指令爲例,在執行時,大致可以分爲以下幾步:

1、先從操作棧中找到對象的實際類型 class;

2、找到 class 中與被調用方法簽名相同的方法,如果有訪問權限就返回這個方法的直接引用,如果沒有訪問權限就報錯 java.lang.IllegalAccessError ;

3、如果第 2 步找不到相符的方法,就去搜索 class 的父類,按照繼承關係自下而上依次執行第 2 步的操作;

4、如果第 3 步找不到相符的方法,就報錯 java.lang.AbstractMethodError ;

可以看到,如果子類覆蓋了父類的方法,則在多態調用中,動態綁定過程會首先確定實際類型是子類,從而先搜索到子類中的方法。這個過程便是方法覆蓋的本質。

實際上,商用虛擬機爲了保證性能,通常會使用虛方法表和接口方法表,而不是每次都執行一遍上面的步驟。以虛方法表爲例,虛方法表在類加載的解析階段填充完成,其中存儲了所有方法的直接引用。也就是說,動態分派在填充虛方法表的時候就已經完成了。

在子類的虛方法表中,如果子類覆蓋了父類的某個方法,則這個方法的直接引用指向子類的實現;而子類沒有覆蓋的那些方法,比如 Object 的方法,直接引用指向父類或 Object 的實現。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章