摘要:重寫語言中的定義子類方法有一個方法與父類方法的名字相同且參數(shù)類型相同。父類方法的返回值可以替換掉子類方法的返回值。思維導(dǎo)圖參考文檔極客時間深入拆解虛擬機(jī)是如何執(zhí)行方法調(diào)用的上廣告
原文
回顧Java語言中的重載與重寫,并且看看JVM是怎么處理它們的。
重載Overload定義:
在同一個類中有多個方法,它們的名字相同,但是參數(shù)類型不同。
或者,父子類中,子類有一個方法與父類非私有方法名字相同,但是參數(shù)類型不同。那么子類的這個方法對父類方法構(gòu)成重載。
JVM是怎么處理重載的?其實(shí)是編譯階段編譯器就已經(jīng)決定好調(diào)用哪一個重載方法??聪旅娲a:
class Overload { void invoke(Object obj, Object... args) { } void invoke(String s, Object obj, Object... args) { } void test1() { // 調(diào)用第二個 invoke 方法 invoke(null, 1); } void test2() { // 調(diào)用第二個 invoke 方法 invoke(null, 1, 2); } void test3() { // 只有手動繞開可變長參數(shù)的語法糖,才能調(diào)用第一個invoke方法 invoke(null, new Object[]{1}); } }
上面的注釋告訴了我們結(jié)果,那么怎么才能證明上面的注釋呢?我們利用javap觀察字節(jié)碼可以知道。
$ javac Overload.java $ javap -c Overload.java Compiled from "Overload.java" class Overload { ... void invoke(java.lang.Object, java.lang.Object...); Code: 0: return void invoke(java.lang.String, java.lang.Object, java.lang.Object...); Code: 0: return void test1(); Code: ... 10: invokevirtual #4 // Method invoke:(Ljava/lang/String;Ljava/lang/Object;[Ljava/lang/Object;)V 13: return void test2(); Code: ... 17: invokevirtual #4 // Method invoke:(Ljava/lang/String;Ljava/lang/Object;[Ljava/lang/Object;)V 20: return void test3(); Code: ... 13: invokevirtual #5 // Method invoke:(Ljava/lang/Object;[Ljava/lang/Object;)V 16: return }
這里面有很多JVM指令,你暫且不用關(guān)心,我們看test1、test2、test3方法調(diào)用的是哪個方法:
void test1(); Code: ... 10: invokevirtual #4 // Method invoke:(Ljava/lang/String;Ljava/lang/Object;[Ljava/lang/Object;)V 13: return
invoke是方法名,(Ljava/lang/String;Ljava/lang/Object;[Ljava/lang/Object;)V則是方法描述符。這里翻譯過來就是void invoke(String, Object, Object[]),Java的可變長參數(shù)實(shí)際上就是數(shù)組,所以等同于void invoke(String, Object, Object...)。同理,test2調(diào)用的是void invoke(String, Object, Object...),test3調(diào)用的是void invoke(Object, Object...)。關(guān)于方法描述符的詳參JVM Spec - 4.3.2. Field Descriptors和JVM Spec - 4.3.3. Method Descriptors。
所以重載方法的選擇是在編譯過程中就已經(jīng)決定的,下面是編譯器的匹配步驟:
不允許自動拆裝箱,不允許可變長參數(shù),嘗試匹配
如果沒有匹配到,則允許自動拆裝箱,不允許可變長參數(shù),嘗試匹配
如果沒有匹配到,則允許自動拆裝箱,允許可變長參數(shù),嘗試匹配
注意:編譯器是根據(jù)實(shí)參類型來匹配,實(shí)參類型和實(shí)際類型不是一個概念
如果在一個步驟里匹配到了多個方法,則根據(jù)形參類型來找最貼切的。在上面的例子中第一個invoke的參數(shù)是Object, Object...,第二個invoke的參數(shù)是String, Object, Object...,兩個方法的第一個參數(shù)String是Object的子類,因此更為貼切,所以invoke(null, 1, 2)會匹配到第二個invoke方法上。
重寫OverrideJava語言中的定義:
子類方法有一個方法與父類方法的名字相同且參數(shù)類型相同。
父類方法的返回值可以替換掉子類方法的返回值。也就是說父類方法的返回值類型:
要么和子類方法返回值類型一樣。
要么是子類方法返回值類型的父類。
兩者都是非私有、非靜態(tài)方法。
(更多詳細(xì)信息可參考Java Language Spec - 8.4.8. Inheritance, Overriding, and Hiding,這里除了有更精確詳細(xì)的重寫的定義,同時包含了范型方法的重寫定義。)
但是JVM中對于重寫的定義則有點(diǎn)不同:
子類方法的名字與方法描述符與父類方法相同。
兩者都是非私有、非靜態(tài)方法。
(更多詳細(xì)信息可參考JVM Spec - 5.4.5. Overriding)
注意上面提到的方法描述符,前面講過方法描述符包含了參數(shù)類型及返回值,JVM要求這兩個必須完全相同才可以,但是Java語言說的是參數(shù)類型相同但是返回值類型可以不同。Java編譯器通過創(chuàng)建Bridge Method來解決這個問題,看下面代碼:
class A { Object f() { return null; } } class C extends A { Integer f() { return null; } }
然后用javap查看編譯結(jié)果:
$ javac Override.java $ javap -v C.class class C extends A ... { java.lang.Integer f(); descriptor: ()Ljava/lang/Integer; flags: Code: stack=1, locals=1, args_size=1 0: aconst_null 1: areturn ... java.lang.Object f(); descriptor: ()Ljava/lang/Object; flags: ACC_BRIDGE, ACC_SYNTHETIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokevirtual #2 // Method f:()Ljava/lang/Integer; 4: areturn LineNumberTable: line 7: 0 }
可以看到編譯器替我們創(chuàng)建了一個Object f()的Bridge Method,它調(diào)用的是Integer f(),這樣就構(gòu)成了JVM所定義的重寫。
思維導(dǎo)圖 參考文檔極客時間 - 深入拆解 Java 虛擬機(jī) - 04 | JVM是如何執(zhí)行方法調(diào)用的?(上)
JVM Spec - 4.3.2. Field Descriptors
JVM Spec - 4.3.3. Method Descriptors
Java Language Spec - 8.4.8. Inheritance, Overriding, and Hiding
Java Language Spec - 8.4.9. Overloading
JVM Spec - 5.4.5. Overriding
Effects of Type Erasure and Bridge Methods
廣告文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/73367.html
摘要:中,任何未處理的受檢查異常強(qiáng)制在子句中聲明。運(yùn)行時多態(tài)是面向?qū)ο笞罹璧臇|西,要實(shí)現(xiàn)運(yùn)行時多態(tài)需要方法重寫子類繼承父類并重寫父類中已 1、簡述Java程序編譯和運(yùn)行的過程:答:① Java編譯程序?qū)ava源程序翻譯為JVM可執(zhí)行代碼--字節(jié)碼,創(chuàng)建完源文件之后,程序會先被編譯成 .class 文件。② 在編譯好的java程序得到.class文件后,使用命令java 運(yùn)行這個 .c...
摘要:中,任何未處理的受檢查異常強(qiáng)制在子句中聲明。運(yùn)行時多態(tài)是面向?qū)ο笞罹璧臇|西,要實(shí)現(xiàn)運(yùn)行時多態(tài)需要方法重寫子類繼承父類并重寫父類中已 1、簡述Java程序編譯和運(yùn)行的過程:答:① Java編譯程序?qū)ava源程序翻譯為JVM可執(zhí)行代碼--字節(jié)碼,創(chuàng)建完源文件之后,程序會先被編譯成 .class 文件。② 在編譯好的java程序得到.class文件后,使用命令java 運(yùn)行這個 .c...
摘要:對應(yīng)的代碼接下來的句是關(guān)鍵部分,兩句分分別把剛剛創(chuàng)建的兩個對象的引用壓到棧頂。所以雖然指令的調(diào)用是相同的,但行調(diào)用方法時,此時棧頂存放的對象引用是,行則是。這,就是語言中方法重寫的本質(zhì)。 類初始化 在講類的初始化之前,我們先來大概了解一下類的聲明周期。如下圖 類的聲明周期可以分為7個階段,但今天我們只講初始化階段。我們我覺得出來使用和卸載階段外,初始化階段是最貼近我們平時學(xué)的,也是筆試...
摘要:也就是說,一個實(shí)例變量,在的對象初始化過程中,最多可以被初始化次。當(dāng)所有必要的類都已經(jīng)裝載結(jié)束,開始執(zhí)行方法體,并用創(chuàng)建對象。對子類成員數(shù)據(jù)按照它們聲明的順序初始化,執(zhí)行子類構(gòu)造函數(shù)的其余部分。 類的拷貝和構(gòu)造 C++是默認(rèn)具有拷貝語義的,對于沒有拷貝運(yùn)算符和拷貝構(gòu)造函數(shù)的類,可以直接進(jìn)行二進(jìn)制拷貝,但是Java并不天生支持深拷貝,它的拷貝只是拷貝在堆上的地址,不同的變量引用的是堆上的...
摘要:網(wǎng)站的面試專題學(xué)習(xí)筆記非可變性和對象引用輸出為,前后皆有空格。假定??臻g足夠的話,盡管遞歸調(diào)用比較難以調(diào)試,在語言中實(shí)現(xiàn)遞歸調(diào)用也是完全可行的。棧遵守規(guī)則,因此遞歸調(diào)用方法能夠記住調(diào)用者并且知道此輪執(zhí)行結(jié)束之返回至當(dāng)初的被調(diào)用位置。 ImportNew 網(wǎng)站的Java面試專題學(xué)習(xí)筆記 1. 非可變性和對象引用 String s = Hello ; s += World ; s.tr...
閱讀 3138·2021-11-24 10:24
閱讀 2930·2021-11-11 16:54
閱讀 3066·2021-09-22 15:55
閱讀 2027·2019-08-30 15:44
閱讀 1901·2019-08-29 18:41
閱讀 2761·2019-08-29 13:43
閱讀 3053·2019-08-29 12:51
閱讀 1172·2019-08-26 12:19