没有与参数列表匹配的 重载函数 getline 实例_面试题:方法重载的底层原理?...
前語:微信改版后,大量讀者還沒養成點贊的習慣,如寫得好,望大家閱讀后在右下邊“好看”處點個贊,以示鼓勵!長期堅持原創真的很不容易,多次想放棄,堅持是一種信仰,專注是一種態度。
關于寫這篇文章,是來自于一個同學在群里拋出這么一道面試題,問執行結果是什么?
public class OverloadTest { static abstract class A{} static class B extends A {} static class C extends A {} public void sayHello(A a){ System.out.println("a"); } public void sayHello(B a){ System.out.println("b"); } public void sayHello(C b){ System.out.println("c"); } // Object 參數 public void say(Object arg) { System.out.println("hello object"); } // int 參數 public void say(int arg) { System.out.println("hello int"); } // long 參數 public void say(long arg) { System.out.println("hello long"); } // char 參數 public void say(char arg) { System.out.println("hello char"); } // Character 參數 public void say(Character arg) { System.out.println("hello character"); } // 變長參數 public void say(char... arg) { System.out.println("hello char..."); } // Serializable 參數 public void say(Serializable arg) { System.out.println("hello serializable"); } public static void main(String[] args) { OverloadTest overloadTest = new OverloadTest(); overloadTest.say('a'); overloadTest.say("a"); A b = new B(); A c = new C(); overloadTest.sayHello(b); overloadTest.sayHello(c); overloadTest.sayHello((B)b); }}輸出的結果如下。
hello charhello serializableaab很明顯涉及到方法重載(overload),為什么會是這個結果?要從我們開始學Java的時說起,那時老師就告訴我們兩個結論。
javac編譯器在編譯階段會根據參數的靜態類型來決定選擇哪個重載版本。
重載優先級,先匹配參數個數;再匹配參數類型的直接所屬類;如果沒有找到直接的所屬類,會向上轉型(包裝類 -> 接口 -> 父類 );如果向上轉型無果,再查找可變參數列表;以上都找不到,則報找不到方法錯誤。
上面提到了靜態類型,我舉列說明一下。
A b = new B();這里的A就是靜態類型,編譯階段可確定;那么相反B就是實際類型,只能運行階段才能確定。
我估計知道答案的同學很多,但要搞明白整個底層原理的同學很少,這里涉及到Java方法底層調用的原理。
方法調用
其實說白了,JVM調用Java程序時,其實也是執行的機器指令,利用字節碼解釋器作為跨越字節碼與機器指令的橋梁,也就是說一個字節碼對應一段特定邏輯的本地機器指令,而JVM在解釋執行字節碼指令時,會直接調用字節碼所對應的機器指令。關于它是怎么調用的?如果你感興趣的話,可以去了解一下C的函數指針,它其實就是將函數指針指向這段機器指令的首地址,從而實現C語言直接調用機器指令的目的(以前寫exp經常這么干)。
我承認上面這段,有點難。
簡而言之,Java調用方法其實用到了字節碼指令,最終查找相應的機器指令,來實現方法的調用。
那么關于方法調用,Java提供了5個字節碼指令。
invokestatic:調用類方法(編譯階段確定方法調用版本)。
invokespecial:調用構造器方法、私有方法及父類方法(編譯階段確定方法調用版本)。
invokevirtual:調用實例方法(虛方法)。
invokeinterface:調用接口方法,在運行再確定一個實現此接口的對象。
invokedynamic:由用戶引導方法決定。
invokestatic和invokespecial指令在類加載時,就能把符號引用(即邏輯地址,與虛擬機內存無關)解析為直接引用,符合這個條件的有靜態方法、實例構造器方法、私有方法、父類方法這4類,叫非虛方法。
非虛方法除了上面靜態方法、實例構造器方法、私有方法、父類方法這4種方法之外,還包括final方法。雖然final方法使用invokevirtual指令來調用,但是final方法無法被覆蓋,沒有其他版本,無需對方法接收者進行多態選擇,或者說多態選擇的結果是唯一的。
底層實現
要看它底層的實現,我們還是得要看字節碼,我通過javap工具把main方法的字節碼給各位展示出來,如下所示。
public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=4, args_size=1 0: new #14 // class com/yrzx404/base/code/OverloadTest 3: dup 4: invokespecial #15 // Method "":()V 7: astore_1 8: aload_1 9: bipush 97 11: invokevirtual #16 // Method say:(C)V 14: aload_1 15: ldc #3 // String a 17: invokevirtual #17 // Method say:(Ljava/io/Serializable;)V 20: new #18 // class com/yrzx404/base/code/OverloadTest$B 23: dup 24: invokespecial #19 // Method com/yrzx404/base/code/OverloadTest$B."":()V 27: astore_2 28: new #20 // class com/yrzx404/base/code/OverloadTest$C 31: dup 32: invokespecial #21 // Method com/yrzx404/base/code/OverloadTest$C."":()V 35: astore_3 36: aload_1 37: aload_2 38: invokevirtual #22 // Method sayHello:(Lcom/yrzx404/base/code/OverloadTest$A;)V 41: aload_1 42: aload_3 43: invokevirtual #22 // Method sayHello:(Lcom/yrzx404/base/code/OverloadTest$A;)V 46: aload_1 47: aload_2 48: checkcast #18 // class com/yrzx404/base/code/OverloadTest$B 51: invokevirtual #23 // Method sayHello:(Lcom/yrzx404/base/code/OverloadTest$B;)V 54: return LineNumberTable: line 67: 0 line 68: 8 line 69: 14 line 71: 20 line 72: 28 line 73: 36 line 74: 41 line 75: 46 line 76: 54 LocalVariableTable: Start Length Slot Name Signature 0 55 0 args [Ljava/lang/String; 8 47 1 overloadTest Lcom/yrzx404/base/code/OverloadTest; 28 27 2 b Lcom/yrzx404/base/code/OverloadTest$A; 36 19 3 c Lcom/yrzx404/base/code/OverloadTest$A;我們這段字節碼指令可以得出,invokevirtual已經確定了調用方法,并且是根據方法參數的靜態類型來決定的。
這里也解決了之前大家的疑問,overloadTest.sayHello((B)b),為什么結果為b?主要在這兩句字節碼指令起的作用。
48: checkcast #18 // class com/yrzx404/base/code/OverloadTest$B51: invokevirtual #23 // Method sayHello:(Lcom/yrzx404/base/code/OverloadTest$B;)V即在強制類型轉換時,會有指令checkcast的調用,而且invokevirtual指令的調用方法也會發生了變化。
關于重寫優先級,這是詹爺他們定下的規定,沒有什么好說的,記住就好了。
最后
相信看到這里,大家應該明白了方法重載。說實話,我個人覺得這道面試題除了為難面試者,沒有什么鳥用!因為實際工作中,沒有誰閑的蛋疼去寫這種“炫技”的代碼。
那么關于方法重寫(override),我這里給一個結論吧,重寫方法的調用主要看實際類型,在運行時決定調用版本。實際類型如果實現了該方法則直接調用該方法,如果沒有實現,則在繼承關系中從低到高搜索有無實現。
那么,希望你也能就這我的思路去分析一下方法重寫。
熬夜寫文章,各位讀者記得在右下角點下【好看】以示鼓勵!
---END---
總結
以上是生活随笔為你收集整理的没有与参数列表匹配的 重载函数 getline 实例_面试题:方法重载的底层原理?...的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 魔鬼恋人原画是谁画的啊?
- 下一篇: spark提交到yarn_详细总结spa