重寫與重載 / 動靜態分派調用(JVM字節碼底層逐步解析,喫雞例子簡單易懂)

面試混分巨獸!網絡千遍一律標準答案如下:

(1)兩者都是面向對象中實現多態的方式,兩者與父類方法名都必須相同,兩者都允許子類根據自己需要重新實現父類方法。
(2)重寫是運行時的多態,重載是編譯時的多態。
(3)重寫必須保持與父類方法一樣的參數列表,一樣的返回值。重載是返回值類型不同或者參數列表不同(參數列表的類型,個數等等)。
背題去面試的足夠了。下面開始講重寫和重載的原理,會有點苦澀:

我曾經也背書式學習,深受其害,直到後來有空了,帶着求知精神深入學習了JVM,才融會貫通很多知識點。

在這裏插入圖片描述

從這個簡單的例子可以看出,子類繼承了父類的方法,可以重寫輸出自己定義的‘說話’;也可以修改父類方法void返回值類型爲string,重載了say方法。下面看看字節碼文件:

在這裏插入圖片描述

編譯後發現有三個class字節碼文件,一個主類。一個父類一個子類。

我們這裏只探究重寫和重載,所以直接看子類:

先看重寫的方法

在這裏插入圖片描述

再到重載的方法:

在這裏插入圖片描述

發現在這裏,它們兩個方法的字節碼還是大同小異的,就是一個很普通的方法執行,沒有特別的標誌和符號或者操作指令說明是重寫還是重載。真正不同的應該是Main方法,也就是調用的地方。它必須知道調用的哪個具體方法,重載的那個還是重寫的那個。

代碼:
在這裏插入圖片描述
字節碼:
在這裏插入圖片描述
在這裏插入圖片描述

可以看見重寫的方法入參類型跟父類的入參類型是一樣的,重載的入參類型則是int類型,這裏就是根據返回值類型或者入參類型或者參數個數去區分。但是子類兩個方法都叫say,卻能夠準確的找到具體哪個say去調用。這是爲什麼?繼續深一步跟蹤:

這裏要加一個概念,就是JVM中方法的調用分兩種

(1)解析調用:一般就是static靜態方法

(2)分派調用:

				### 動態調用又分:靜態分派調用(重載)和動態分派調用(重寫)
***靜態分派調用***就是重載的方法,它之所以說是靜態分派,就是因爲它在程序的編譯階段就已經通過參數和方法名或者返回值就能確定調用子類的哪個重載的具體方法,是在編譯期確定的,所以叫靜態分派。如果有多個都相符合的,比如一個重載方法入參是char ,一個是long,一個是Object.當你傳 “a” 的時候,就會準確找到int,當沒有入參類型是char的重載方法時,纔會遷就一點去找long或者Obeject的(中間會有強轉型和如果是對象類型包裝類型的裝箱)。
比如你玩喫雞,一開始跳傘你就確定了地圖上要撿哪一把槍,目標明確。但是當你想找一把mini狙找不到,那麼98K你也將就着先用,因爲都是相同類型,就算找到空投裏面有AWM,但是你還是用mini順手,所以你還是將就用着,有最合適的就換。
***動態分派調用***就是重寫的方法或者子類調用父類的方法,它在編譯期間沒辦法根據方法名和參數或者返回值確定具體方法,而是在運行時纔去主動找要哪個方法。JVM有一個調用過程:

(1)invoke字節碼操作指令調用方法時,先去找操作數棧中頂部的棧幀元素指向的對象的類型(方法就是棧裏面一個個棧幀),這裏可以想象成先從子類找有沒有重寫這個方法。
(2)假如這個棧幀中是描述符和簡單名稱都相符合的方法,則進行訪問權限的校驗,如果校驗通過,則直接返回這個方法的直接引用。
(3)如果不存在,則根據繼承的父子關係對父類繼續進行查找(描述符和簡單名稱都相符,並且權限允許),如果一個父類不存在,則繼續找父類,繼續父類的父類。
(4)如果全部父類都沒有,返回一個抽象方法未實現異常(AbstractMethodError)。

比如玩喫雞的時候,你開局跳傘時根本沒有想着一定要打什麼武器,而是在作戰的時候,根據地形和戰況再選擇武器,攻房用衝鋒,近戰噴子,遠打狙,守橋時用汽油之類。

好了,重寫重載就說到這裏,如果我有說的不對的地方歡迎提出來,這都是我根據自己的學習理解總結的,歡迎大家討論,鞠躬!

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