不知道有沒有小伙伴在面試時被問到過方法重寫(Override)和重載(Overload)的區(qū)別?反正我是被問起過數(shù)次佳镜,大概情況是這樣的:
面試官:說下Override和Overload的區(qū)別奥喻?
我:(內(nèi)心:額明也,送人頭的选浑,然后就)方法重寫發(fā)生在子畜份、父繼承的關(guān)系下办素,子類可以修改父類的方法角雷,以達到增強、擴展等~~#¥%@ bala bala
面試官:嗯性穿,還有嗎勺三?
我:(xx 一緊,還有啥需曾?努力回想是不是忘說了啥)額吗坚,就這些吧
面試官:恩,今天面試就到這吧呆万,你回去等消息吧
我:#¥%@~
方法重寫和重載商源,相信只要是剛接觸過java語言,對這兩個概念就不會陌生谋减,遇到相關(guān)的面試題估計也不少牡彻,今天我們就從面試題下手缎除,然后再分析到其字節(jié)碼層面总寻,對這兩個概念做一個介紹。
首先貼上面試題
// 父類
public class Parent {
int age = 40;
public void walk() {
System.out.println("parent walk");
}
}
// 子類
public class Child extends Parent {
int age = 15;
public void walk() {
System.out.println("child walk");
}
}
// 測試重載
public class TestOverload {
public void method(Parent parent) {
System.out.println("parent");
}
public void method(Child child) {
System.out.println("child");
}
public static void main(String[] args) {
TestOverload testOverload = new TestOverload();
Parent parent = new Child();
testOverload.method(parent);
Child child = new Child();
testOverload.method(child);
}
}
相信機智如你,早就知道了答案衰倦,結(jié)果:
parent
child
Process finished with exit code 0
不多解釋概念,先javap看下字節(jié)碼(為節(jié)省篇幅,只貼出部分常量池和main字節(jié)碼)
Constant pool:
#1 = Methodref #12.#34 // java/lang/Object."<init>":()V
#2 = Fieldref #35.#36 // java/lang/System.out:Ljava/io/PrintStream;
#3 = String #22 // parent
#4 = Methodref #37.#38 // java/io/PrintStream.println:(Ljava/lang/String;)V
#5 = String #25 // child
#6 = Class #39 // com/jvm/learnjvm/test/TestOverload
#7 = Methodref #6.#34 // com/jvm/learnjvm/test/TestOverload."<init>":()V
#8 = Class #40 // com/jvm/learnjvm/test/Child
#9 = Methodref #8.#34 // com/jvm/learnjvm/test/Child."<init>":()V
#10 = Methodref #6.#41 // com/jvm/learnjvm/test/TestOverload.method:(Lcom/jvm/learnjvm/test/Parent;)V
#11 = Methodref #6.#42 // com/jvm/learnjvm/test/TestOverload.method:(Lcom/jvm/learnjvm/test/Child;)V
#12 = Class #43 // java/lang/Object
#13 = Utf8 <init>
public static void main(java.lang.String[]); // main 方法
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=4, args_size=1
0: new #6 // class com/jvm/learnjvm/test/TestOverload
3: dup
4: invokespecial #7 // Method "<init>":()V
7: astore_1
8: new #8 // class com/jvm/learnjvm/test/Child
11: dup
12: invokespecial #9 // Method com/jvm/learnjvm/test/Child."<init>":()V
15: astore_2
16: aload_1
17: aload_2
18: invokevirtual #10 // Method method:(Lcom/jvm/learnjvm/test/Parent;)V
21: new #8 // class com/jvm/learnjvm/test/Child
24: dup
25: invokespecial #9 // Method com/jvm/learnjvm/test/Child."<init>":()V
28: astore_3
29: aload_1
30: aload_3
31: invokevirtual #11 // Method method:(Lcom/jvm/learnjvm/test/Child;)V
我們重點看一下main方法的 18: invokevirtual #10
和31: invokevirtual #11
,執(zhí)行的方法肥隆,對照常量池的#10 = Methodref #6.#41 // com/jvm/learnjvm/test/TestOverload.method:(Lcom/jvm/learnjvm/test/Parent;)V
,
#11 = Methodref #6.#42 // com/jvm/learnjvm/test/TestOverload.method:(Lcom/jvm/learnjvm/test/Child;)V
句各,通過方法入?yún)⒎停梢钥吹胶芮宄目吹秸{(diào)用的方法情況,因為此時并沒有運行绪杏,不知道將來傳入的參數(shù)真實實例是什么,編譯器只是根據(jù)聲明的參數(shù)的類型和數(shù)量等匹配到合適的重載方法僧著,這種方式,被稱為“靜態(tài)分派”皆怕。
同樣在編譯期確定的還有調(diào)用成員變量的經(jīng)典面試題岂津,有興趣的可以自己看下字節(jié)碼分析下
public class TestOverload {
public void method(Parent parent) {
System.out.println("parent");
}
public void method(Child child) {
System.out.println("child");
}
public static void main(String[] args) {
TestOverload testOverload = new TestOverload();
Parent parent = new Child();
System.out.println(parent.age);
Child child = new Child();
System.out.println(child.age);
}
}
結(jié)果
40
15
Process finished with exit code 0
分析完方法重載粱甫,現(xiàn)在分析下方法重寫粉洼,先來一段大家都熟到不能再熟的代碼(Parent和Child類依舊使用之前的)
public class TestOverride {
public static void main(String[] args) {
Parent parent = new Child();
parent.walk();
}
}
大家用腳指頭想都知道的結(jié)果
child walk
Process finished with exit code 0
面對感覺理所應(yīng)當?shù)慕Y(jié)果,我們還是先看看字節(jié)碼吧
Constant pool:
#1 = Methodref #6.#22 // java/lang/Object."<init>":()V
#2 = Class #23 // com/jvm/learnjvm/test/Child
#3 = Methodref #2.#22 // com/jvm/learnjvm/test/Child."<init>":()V
#4 = Methodref #24.#25 // com/jvm/learnjvm/test/Parent.walk:()V
#5 = Class #26 // com/jvm/learnjvm/test/TestOverride
#6 = Class #27 // java/lang/Object
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=2, args_size=1
0: new #2 // class com/jvm/learnjvm/test/Child
3: dup
4: invokespecial #3 // Method com/jvm/learnjvm/test/Child."<init>":()V
7: astore_1
8: aload_1
9: invokevirtual #4 // Method com/jvm/learnjvm/test/Parent.walk:()V
12: return
執(zhí)行方法調(diào)用的是9: invokevirtual #4
拙泽,指向的是常量池#4
,然后我們絲毫不慌的去常量看看荷荤,
#4 = Methodref #24.#25 // com/jvm/learnjvm/test/Parent.walk:()V
what退渗??会油?調(diào)用的是Parent的walk() 古毛?氣氛突然有些尷尬......
其實這個時候体斩,就要說下面向?qū)ο蟮娜筇匦裕悍庋b蹬敲、繼承瘪校、多態(tài)中的多態(tài)的實現(xiàn)原理了。多態(tài)是什么不用我說大家也都知道名段,就不解釋概念了阱扬,從字節(jié)碼角度說一下,還是回到main方法的字節(jié)碼再看一下信夫,前面主要是創(chuàng)建對象窃蹋,執(zhí)行構(gòu)造方法卡啰,并把當前創(chuàng)建的對象實例壓到操作數(shù)棧,重點看下9警没,執(zhí)行invokevirtual
指令匈辱,(jdk提供了5條方法調(diào)用的指令,在最下面有列出),invokevirtual
指令是找到當前操作數(shù)棧棧頂元素指向的對象的實際類型杀迹,也就是new出來的Child梅誓,然后執(zhí)行該指令對應(yīng)的常量池中的方法#4
,而這時候是運行期佛南,jvm會根據(jù)方法的名稱和描述來定位方法梗掰,調(diào)用的是Child實例的walk,這種動態(tài)調(diào)用方法的方式嗅回,也被稱為動態(tài)分派及穗。
方法調(diào)用指令
invokestatic
?? ??????調(diào)用靜態(tài)方法
invokevirtual
???????調(diào)用實例方法
invokespecial
????????調(diào)用私有方法、實例構(gòu)造方法绵载、super()
invokeinterface
????調(diào)用引用類型為interface的實例方法
invokedynamic
???????JDK 7引入的埂陆,主要是為了支持動態(tài)語言的方法調(diào)用,如Lambda