Java及JVM是如何識別重載、重寫方法的?

      網友投稿 876 2025-04-01

      可變長參數方法的重載造成的。(官方文檔建議避免重載可變長參數方法,見[1]的最后一段。

      案例

      void invoke(Object obj, Object... args) { ... } void invoke(String s, Object obj, Object... args) { ... } invoke(null, 1); // 調用第二個invoke方法 invoke(null, 1, 2); // 調用第二個invoke方法 invoke(null, new Object[]{1}); // 只有手動繞開可變長參數的語法糖, // 才能調用第一個invoke方法

      1

      2

      3

      4

      5

      6

      Java及JVM是如何識別重載、重寫方法的?

      7

      某API定義了兩個同名重載方法:

      第一個接收一個Object,以及聲明為Object…的變長參數

      第二個則接收一個String、一個Object,以及聲明為Object…的變長參數

      想調用第一個方法,傳參(null, 1),即聲明為Object的形式參數所對應的實際參數為null,而變長參數則對應1。

      之所以不提倡可變長參數方法重載,是因為Java編譯器可能無法決定應該調用哪個目標方法。

      這種情況下,編譯器會報錯,并且提示這方法調用有二義性。然而,Java編譯器直接將我的方法調用識別為調用第二個方法,這究竟是為什么呢?

      Java虛擬機是怎么識別目標方法的?

      重載與重寫

      同一類中出現多個:

      名字相同

      參數類型相同

      的方法,則無法編譯。如想在同一個類中定義名字相同方法,它們參數類型必須不同。這些方法之間的關系稱為重載。

      這限制可通過字節碼工具繞開,編譯完成后,可再向class文件中添加方法名和參數類型相同,而返回類型不同的方法。當這種包括多個方法名相同、參數類型相同,而返回類型不同的方法的類,出現在Java編譯器的用戶類路徑上時,它是怎么確定需要調用哪個方法的呢?

      當前版本的Java編譯器會直接選取第一個方法名以及參數類型匹配的方法。并且,它會根據所選取方法的返回類型來決定可不可以通過編譯,以及需不需要進行值轉換等。

      重載的方法在編譯過程中即可完成識別。具體到每一個方法調用,Java編譯器會根據所傳入參數的聲明類型(注意與實際類型區分)來選取重載方法。選取的過程共分為三個階段:

      在不考慮對基本類型自動裝拆箱及可變長參數情況下選取重載方法

      如在第1個階段沒找到適配方法,那在允許自動裝拆箱,但不允許可變長參數情況下選取重載方法

      如在第2個階段中沒找到適配方法,那在允許自動裝拆箱及可變長參數情況下選取重載方法

      如Java編譯器在同一階段中找到多個適配方法,那它會在其中選擇一個最為貼切,貼切程度關鍵就是形式參數類型的繼承關系。

      傳入null時,它既可匹配第一個方法中聲明為Object的形式參數,也可匹配第二個方法中聲明為String的形式參數。由于String是Object的子類,因此Java編譯器會認為第二個方法更貼切。

      除同一個類中的方法,重載也可作用于這個類所繼承而來的方法。如子類定義了與父類中非私有方法同名的方法,且這兩個方法的參數類型不同,那在子類中,這兩個方法同樣構成重載。

      若子類定義與父類中非private方法的同名方法,且這兩方法參數類型相同,那這倆方法間啥關系:

      若這倆都是static方法,那子類中的方法隱藏了父類中的方法

      若都不是 static 的,則子類的方法重寫了父類中的方法

      Java的方法重寫是多態的體現:允許子類在繼承父類部分功能同時,擁有自己獨特行為。

      重寫調用會根據調用者的動態類型選取實際的目標方法。

      JVM的靜態綁定和動態綁定

      Java虛擬機識別方法的關鍵在于類名、方法名及方法描述符(method descriptor)。

      方法描述符由方法的參數類型及返回類型構成。

      同一類中,如同時出現多個名字相同且描述符相同的方法,那Java虛擬機會在類的驗證階段報錯。

      Java虛擬機與Java語言不同,它不限制名字與參數類型相同,但返回類型不同的方法出現在同一類,對調用這些方法的字節碼,由于字節碼所附帶的方法描述符包含了返回類型,因此Java虛擬機能夠準確識別目標方法。

      JVM方法重寫判定同樣基于方法描述符。

      如子類定義了與父類中非私有、非靜態方法同名的方法,則僅當這倆方法的參數類型及返回類型一致,JVM才會判定為重寫。

      對Java中重寫而Java虛擬機中非重寫的情況,編譯器會通過生成橋接方法[2]實現Java的重寫語義。

      由于對重載方法的區分在編譯階段已完成,可認為JVM不存在重載概念。因此,某些文章將

      重載稱為靜態綁定(static binding)或編譯時多態(compile-time polymorphism)

      重寫稱為動態綁定(dynamic binding)

      這說法在JVM語境下并非完全正確,因為某類中的重載方法可能被它的子類重寫,因此JVM 會將所有對非私有實例方法的調用編譯為需要動態綁定的類型。

      JVM的:

      靜態綁定指在解析時便能夠直接識別目標方法

      動態綁定指要在運行過程中,根據調用者的動態類型來識別目標方法

      Java字節碼中與調用相關的指令有:

      invokestatic:調用靜態方法

      invokespecial:調用私有實例方法、構造器及使用super關鍵字調用父類的實例方法或構造器,和所實現接口的默認方法

      invokevirtual:用于調用非私有實例方法

      invokeinterface:用于調用接口方法

      invokedynamic:用于調用動態方法

      較為復雜

      編譯生成這四種調用指令的情況。

      interface 客戶 { boolean isVIP(); } class 商戶 { public double 折后價格(double 原價, 客戶 某客戶) { return 原價 * 0.8d; } } class 奸商 extends 商戶 { @Override public double 折后價格(double 原價, 客戶 某客戶) { if (某客戶.isVIP()) { // invokeinterface return 原價 * 價格歧視(); // invokestatic } else { return super.折后價格(原價, 某客戶); // invokespecial } } public static double 價格歧視() { // 咱們的殺熟算法太粗暴了,應該將客戶城市作為隨機數生成器的種子。 return new Random() // invokespecial .nextDouble() // invokevirtual + 0.8d; } }

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      12

      13

      14

      15

      16

      17

      18

      19

      20

      21

      22

      23

      24

      25

      26

      “商戶”類定義了一個成員方法,叫“折后價格”,它接收一個double類型參數及一個“客戶”類型參數。

      這里“客戶”是個接口,定義了一個接口方法“isVIP”。

      “奸商”類這個方法,首先調用客戶#isVIP,該調用會被編譯為invokeinterface指令

      若客戶是VIP,則調用奸商類的一個名叫“價格歧視”的靜態方法。該調用會被編譯為invokestatic指令

      如客戶不是VIP,則通過super調用父類的“折后價格”方法。該調用會被編譯為invokespecial指令

      在靜態方法“價格歧視”會調用Random類的構造器。該調用會被編譯為invokespecial指令。然后以這個新建Random對象為調用者,調用Random類中的nextDouble方法。該調用會被編譯為invokevirutal指令。

      對于invokestatic以及invokespecial而言,Java虛擬機能夠直接識別具體的目標方法。

      而對于invokevirtual以及invokeinterface而言,在絕大部分情況下,虛擬機需要在執行過程中,根據調用者的動態類型,來確定具體的目標方法。

      如虛擬機能確定目標方法有且僅有一個,比如說目標方法被標記為final[3][4],它可不通過動態類型,直接確定目標方法。

      調用指令的符號引用

      編譯過程中,我們并不知目標方法的具體內存地址。因此,Java編譯器會暫時用符號引表示該目標方法。

      這符號引用包括目標方法所在的類或接口的名字,以及目標方法的方法名和方法描述符。

      符號引用存儲在class文件的常量池。根據目標方法是否為接口方法,這些引用可分為:

      接口符號引用

      非接口符號引用

      // 在奸商.class的常量池中,#16為接口符號引用,指向接口方法"客戶.isVIP()"。#22為非接口符號引用,指向靜態方法"奸商.價格歧視()"。 $ javap -v 奸商.class ... Constant pool: ... #16 = InterfaceMethodref #27.#29 // 客戶.isVIP:()Z ... #22 = Methodref #1.#33 // 奸商.價格歧視:()D ...

      1

      2

      3

      4

      5

      6

      7

      8

      執行使用了符號引用的字節碼前,JVM需解析這些【符號引用】并替換為【實際引用】。

      對【非接口符號引用】,假定該【符號引用】所指向的類為C,則JVM按如下步驟查找:

      在C中查找符合名字及描述符的方法

      若沒找到,搜索C的父類,直至Object類

      若還沒找到,在C所直接實現或間接實現的接口中搜索,該步搜索得到的目標方法必須是非private、非static且若目標方法在間接實現的接口中,則需滿足C與該接口間無其他符合條件的目標方法。若有多個符合條件的目標方法,則返回其中任一。

      所以static方法也可通過子類來調用。子類的static方法會隱藏(這不是重寫)父類中的同名、同描述符的靜態方法。

      對于接口符號引用,假定該符號引用所指向的接口為I,則Java虛擬機會按照如下步驟進行查找。

      在I中查找符合名字及描述符的方法。

      如果沒有找到,在Object類中的公有實例方法中搜索。

      如果沒有找到,則在I的超接口中搜索。這一步的搜索結果的要求與非接口符號引用步驟3的要求一致。

      經過上述解析步驟后,符號引用會被解析成實際引用:

      對可靜態綁定的方法調用,實際引用是個指向方法的指針

      對需動態綁定的方法調用,實際引用則是個方法表的索引

      總結與實踐

      本文介紹了Java以及Java虛擬機是如何識別目標方法的。

      在Java方法的:

      重載,方法名相同而參數類型不相同的方法間

      重寫,方法名相同&參數類型也相同的方法間

      JVM識別方法的方式除了方法名和參數類型,還有返回類型。

      JVM的:

      靜態綁定:在解析時便能夠直接識別目標方法的情況

      動態綁定,需在運行過程中根據調用者的動態類型來識別目標方法的情況。由于Java編譯器已區分重載方法,因此可認為JVM不存在重載

      在class文件中,Java編譯器會用符號引用指代目標方法。在執行調用指令前,它所附帶的符號引用需要被解析成實際引用。對于可以靜態綁定的方法調用而言,實際引用為目標方法的指針。對于需要動態綁定的方法調用而言,實際引用為輔助動態綁定的信息。

      Java的重寫與Java虛擬機中的重寫并不一致,但編譯器會通過生成橋接方法來彌補。

      參考

      [1] https://docs.oracle.com/javase/8/docs/technotes/guides/language/varargs.html

      [2] https://docs.oracle.com/javase/tutorial/java/generics/bridgeMethods.html

      [3] https://wiki.openjdk.java.net/display/HotSpot/VirtualCalls

      [4] https://wiki.openjdk.java.net/display/HotSpot/InterfaceCalls

      Java JVM

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      上一篇:Excel也可以這樣智能化一招讓你的Excel 報表自己會說話(excel十大功能)
      下一篇:一個文件,有3頁,只想后兩頁打上水印,做不到(為什么文檔中有幾頁加不了水印)
      相關文章
      中文无码亚洲精品字幕| 亚洲AV无码精品无码麻豆| 亚洲AⅤ视频一区二区三区 | 亚洲国产精品自在在线观看| 亚洲乱码国产一区网址| 色欲aⅴ亚洲情无码AV蜜桃| 亚洲a级在线观看| 亚洲国产精品综合久久网各 | 亚洲色欲色欲www在线播放| 亚洲第一页在线视频| 亚洲av日韩综合一区在线观看| 亚洲无av在线中文字幕| 亚洲无码日韩精品第一页| 色婷婷六月亚洲综合香蕉| 亚洲高清一区二区三区电影| 国产成人亚洲综合一区| 亚洲精品亚洲人成在线播放| 亚洲videosbestsex日本| 亚洲av永久综合在线观看尤物| 久久亚洲高清综合| 亚洲黄色片免费看| 国产亚洲精品拍拍拍拍拍| 无码天堂亚洲国产AV| 亚洲成在人线在线播放无码| 亚洲色偷偷综合亚洲av78| 亚洲三级在线视频| 亚洲日产2021三区在线| 自怕偷自怕亚洲精品| 亚洲成熟xxxxx电影| 亚洲va久久久噜噜噜久久天堂| 亚洲男人在线无码视频| 亚洲国产精品嫩草影院久久| 国产午夜亚洲精品不卡电影| 女bbbbxxxx另类亚洲| 国产精品亚洲二区在线| 亚洲AV成人无码久久精品老人| 亚洲春色在线视频| 亚洲成无码人在线观看| 亚洲无人区码一二三码区别图片| 国产精品亚洲五月天高清| 自拍偷自拍亚洲精品被多人伦好爽|