日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

JVM之方法调用-分派

發(fā)布時間:2024/3/26 编程问答 26 豆豆
生活随笔 收集整理的這篇文章主要介紹了 JVM之方法调用-分派 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

說明:這兩天遇到的一些Java方法分派的問題,結(jié)合自己書上看的,google的,還有撒迦教我的,做一個總結(jié)吧.望指正.

?

寫道 方法分派指的是虛擬機如何確定應(yīng)該執(zhí)行哪個方法!

?

很多的內(nèi)容可以參加撒迦的這篇博文 :?http://rednaxelafx.iteye.com/blog/652719

我這篇里很多概念的解釋都摘自上面的博文,所以,我就不一一指出啦.在此感謝撒迦的幫助.

還有一些講解(包括代碼)來自 <深入JAVA虛擬機-jvm高級特性與最佳實踐>,很好一本書,推薦對jvm有興趣的同學(xué)購買

另外?http://hllvm.group.iteye.com/group/topic/27064?這個帖子也可能幫助大家對方法分派有所了解.

1 靜態(tài)分派

??? 對于這個靜態(tài)分派,撒迦很喜歡叫做非虛方法分派(當然按照他自己的說法就是:我不喜歡叫靜態(tài)分派).

??? 首先,解釋一下什么叫虛方法.虛方法的概念有點難說,不過把什么是非虛方法說明一下,其他的就是虛方法啦.哈哈

非虛方法是所有的類方法(也就是申明為static的方法) + 所有聲明為final或private的實例方法.

?

??? 由于非虛方法不能被override,所以自然也不會產(chǎn)生子類復(fù)寫的多態(tài)效果.這樣的話,方法被調(diào)用的入口只可能是一個.而且編譯器可知.也就是說,jvm需要執(zhí)行哪個方法是在編譯器就已經(jīng)確定.且在運行期不會變化.很具體的例子就是方法的重載.

??? 看如下例子,摘自 <深入JAVA虛擬機-jvm高級特性與最佳實踐>

?

Java代碼??
  • public?class?StaticDispatch?{??
  • ??????
  • ????static?abstract?class?Human{??
  • ??????????
  • ????}??
  • ??????
  • ????static?class?Man?extends?Human{??
  • ??????????
  • ????}??
  • ??????
  • ????static?class?Woman?extends?Human{??
  • ??????????
  • ????}??
  • ??
  • ????public?void?sayHello(Human?human){??
  • ????????System.out.println("human?say?hello");??
  • ????}??
  • ??????
  • ????public?void?sayHello(Man?man){??
  • ????????System.out.println("man?say?hello");??
  • ????}??
  • ??????
  • ????public?void?sayHello(Woman?woman){??
  • ????????System.out.println("woman?say?hello");??
  • ????}??
  • ??????
  • ????/**?
  • ?????*?@param?args?
  • ?????*/??
  • ????public?static?void?main(String[]?args)?{??
  • ????????Human?man?=?new?Man();??
  • ????????Human?woman?=?new?Woman();??
  • ????????StaticDispatch?sd?=?new?StaticDispatch();??
  • ????????sd.sayHello(man);??
  • ????????sd.sayHello(woman);??
  • ????}??
  • ??
  • }??
  • ?

    最后的輸出是

    console 寫道 human say hello?
    human say hello

    ?

    這個就是很典型的靜態(tài)分派.看這段代碼

    ?

    Human man = new Man();?
    Human woman = new Woman();

    ????

    ???? 其中的Human 稱為變量的靜態(tài)類型,而后面的Man稱為變量的實際類型. 靜態(tài)類型是在編譯器可見的,而動態(tài)類型必須在運行期才知道.再分析這段調(diào)用的方法

    ??

    StaticDispatch sd = new StaticDispatch();?
    sd.sayHello(man);?
    sd.sayHello(woman);

    ?

    ??? 我們看到,調(diào)用方法的接受者是確定的,都是sd.在靜態(tài)分派中,jvm如何確定具體調(diào)用哪個目標方法就完全取決于傳入?yún)?shù)的數(shù)量和數(shù)據(jù)類型.而且是根據(jù)數(shù)據(jù)的靜態(tài)類型..正因為如此,這兩個sayHello方法,最后都調(diào)用了public void sayHello(Human human);方法.

    ?

    ?? 但是,仔細看會發(fā)現(xiàn),我舉的這個例子,雖然確實是通過靜態(tài)分派的,但是具體的方法卻是虛方法..也就是說,

    虛方法也可能是被靜態(tài)分派的.特別注意,重載就是通過靜態(tài)分派的

    ?

    ?? 其實非虛方法的靜態(tài)分派是完全合理的,后面會再舉一個例子,來確定只要是非虛方法,肯定是通過靜態(tài)分派的.

    ?? 本節(jié)最后的問題是

    寫道 Java語言中方法重載采用靜態(tài)分派是JVM規(guī)范規(guī)定的還是語言級別的規(guī)定?

    ?

    ??? 這個問題曾經(jīng)讓我有過困惑.因為上面這個重載的例子中,

    Java代碼??
  • sd.sayHello(man);???
  • sd.sayHello(woman);??
  • ??? 這兩個sayHello方法都是用invokevirtual 指令(關(guān)于這個指令,后面會開專門的一節(jié)說明)的,那么其實完全可以采用動態(tài)分派,根據(jù)man 和 woman 的實際類型來決定調(diào)用哪個方法.但是實際上jvm缺沒這么做.一直等我在仔細看了Java語言"單分派還是多分派"這個內(nèi)容以后,才有了答案.下面會專門開一節(jié)說這個單分派和多分派.這個問題也在后面解答.?

    ?

    2 動態(tài)分派?

    ??? 可以說,動態(tài)方法分派是Java實現(xiàn)多態(tài)的一個重要基礎(chǔ).因為,它是Java多態(tài)之一----重寫的基礎(chǔ).看下面的代碼,,摘自 <深入JAVA虛擬機-jvm高級特性與最佳實踐>

    Java代碼??
  • public?class?DynamicDispatch?{??
  • ??????
  • ????static?abstract?class?Human{??
  • ????????protected?abstract?void?sayHello();??
  • ????}??
  • ??????
  • ????static?class?Man?extends?Human{??
  • ??
  • ????????@Override??
  • ????????protected?void?sayHello()?{??
  • ????????????System.out.println("man?say?hello");??
  • ????????}??
  • ??????????
  • ????}??
  • ??????
  • ????static?class?Woman?extends?Human{??
  • ??
  • ????????@Override??
  • ????????protected?void?sayHello()?{??
  • ????????????System.out.println("woman?say?hello");??
  • ????????}??
  • ??????????
  • ????}??
  • ??
  • ????/**?
  • ?????*?@param?args?
  • ?????*/??
  • ????public?static?void?main(String[]?args)?{??
  • ????????Human?man?=?new?Man();??
  • ????????Human?woman?=?new?Woman();??
  • ????????man.sayHello();??
  • ????????woman.sayHello();??
  • ????}??
  • ??
  • }??
  • ?

    console 寫道 man say hello?
    woman say hello?

    ?

    ??? 只要有一點Java基礎(chǔ)的人基本都能看懂這段代碼.一個非常簡單的重寫.具體看它的結(jié)果,很明顯這里已經(jīng)不是靜態(tài)分派了.因為man和woman在編譯器都是Human類型,如果是靜態(tài)分派,那么這兩個調(diào)用的方法應(yīng)該是同一個.但是實際上,它們卻調(diào)用了對應(yīng)的真實類型的方法.這就是動態(tài)分派.

    ?

    3 invokespecial和invokevirtual指令

    ??? 說這個,最主要是由于上面說的那個討論引起的(詳情http://hllvm.group.iteye.com/group/topic/27064).代碼還是放上來吧.

    ?

    ???代碼1

    Java代碼??
  • public?class?SuperTest?{??
  • ????public?static?void?main(String[]?args)?{??
  • ????????new?Sub().exampleMethod();??
  • ????}??
  • }??
  • ??
  • class?Super?{??
  • ????<span?style="color:?#ff0000;">private</span>?void?interestingMethod()?{??
  • ????????System.out.println("Super's?interestingMethod");??
  • ????}??
  • ??
  • ????void?exampleMethod()?{??
  • ????????interestingMethod();??
  • ????}??
  • }??
  • ??
  • class?Sub?extends?Super?{??
  • ??
  • ????void?interestingMethod()?{??
  • ????????System.out.println("Sub's?interestingMethod");??
  • ????}??
  • }??
  • ?

    console輸出 Super's?interestingMethod?

    ?

    ??代碼2

    Java代碼??
  • public?class?SuperTest?{??
  • ????public?static?void?main(String[]?args)?{??
  • ????????new?Sub().exampleMethod();??
  • ????}??
  • }??
  • ??
  • class?Super?{??
  • ????void?interestingMethod()?{??
  • ????????System.out.println("Super's?interestingMethod");??
  • ????}??
  • ??
  • ????void?exampleMethod()?{??
  • ????????interestingMethod();??
  • ????}??
  • }??
  • ??
  • class?Sub?extends?Super?{??
  • ??
  • ????void?interestingMethod()?{??
  • ????????System.out.println("Sub's?interestingMethod");??
  • ????}??
  • }??
  • ?

    console輸出 寫道 Sub's?interestingMethod

    ?

    ??? 代碼一與代碼二,只有一個區(qū)別,就是在代碼一中Super類的interestingMethod方法的修飾符多一個private.根據(jù)執(zhí)行最后的結(jié)果來看卻是直接造成了方法分派的不同.一個執(zhí)行了父類的interestingMethod方法,而一個執(zhí)行了子類的interestingMethod方法.

    ??? 對于這個例子,撒迦的回答比較明確

    寫道 關(guān)鍵點在于“Java里什么是虛方法”以及“虛方法如何分派”。?
    Java里只有非private的成員方法是虛方法。?

    所以你會留意到在頂樓例子的第一個版本里,exampleMethod()是用invokespecial來調(diào)用interestingMethod()的;而第二個版本里則是用invokevirtual。

    ??? 在本文的開頭已經(jīng)解釋了"什么是虛方法"這個問題.可以知道,代碼一中Super類的interestingMethod方法是非虛方法(因為第一個是private方法),而代碼二則是虛方法.可以明確的是

    ?

    寫道 非虛方法肯定是用靜態(tài)分派

    ?

    ??? 所以,在代碼一中,使用靜態(tài)分派,Super類中的exampleMethod方法調(diào)用的是自己類中的interestingMethod方法.這個是編譯器就已經(jīng)確定的.而代碼二中,exampleMethod方法執(zhí)行哪個interestingMethod方法就需要看真實對象是哪個.在本例中,真實對象肯定是Sub類.所以就調(diào)用Sub類的interestingMethod方法.

    ???

    ??? 上面的這一段分析很簡單,我們可以通過javap輸出看看對應(yīng)的信息(只需要看Super類的輸出就可以了.代碼一和代碼二的唯一區(qū)別就是Super類的interestingMethod方法修飾符)

    ??????

    ?

    代碼一的Super類javap輸出 Compiled from "SuperTest.java"?
    class Super {?
    Super();?
    Code:?
    0: aload_0?
    1: invokespecial #1 // Method java/lang/Object."<init>":?
    ()V?
    4: return?

    void exampleMethod();?
    Code:?
    0: aload_0?
    1: ldc #5 // String aa?
    3:?invokespecial?#6 // Method interestingMethod:(Ljava/l?
    ang/String;)I?
    6: pop?
    7: return?
    }

    ?

    代碼二的Super類javap輸出 寫道 Compiled from "SuperTest.java"?
    class Super {?
    Super();?
    Code:?
    0: aload_0?
    1: invokespecial #1 // Method java/lang/Object."<init>":?
    ()V?
    4: return?

    int interestingMethod(java.lang.String);?
    Code:?
    0: getstatic #2 // Field java/lang/System.out:Ljava/?
    io/PrintStream;?
    3: ldc #3 // String Super's interestingMethod?
    5: invokevirtual #4 // Method java/io/PrintStream.printl?
    n:(Ljava/lang/String;)V?
    8: iconst_1?
    9: ireturn?

    void exampleMethod();?
    Code:?
    0: aload_0?
    1: ldc #5 // String aa?
    3:?invokevirtual?#6 // Method interestingMethod:(Ljava/l?
    ang/String;)I?
    6: pop?
    7: return?
    }

    ?

    ??? 兩邊的不同我通過加粗來說明了.正如撒迦說的,在Super類的exampleMethod方法中調(diào)用interestingMethod方法的指令是不同的,代碼一采用的是invokespecial?而代碼二采用的是invokevirtual .

    ?

    寫道 · invokespecial - super方法調(diào)用、private方法調(diào)用與構(gòu)造器調(diào)用?
    · invokevirtual - 用于調(diào)用一般實例方法(包括聲明為final但不為private的實例方法)?

    ????

    其中

    ???

    寫道 invokespecial調(diào)用的目標必然是可以靜態(tài)綁定的,因為它們都無法參與子類型多態(tài);invokevirtual的則一般需要做運行時綁定

    ?

    ??? 到這里,我們可以明確的是,使用invokespecial 指令的肯定是靜態(tài)方法分配的,但是使用invokevirtual卻還不一定()..我們可以看一下本文說靜態(tài)分配的那個例子的javap輸出(StaticDispatch類)

    ?

    StaticDispatch類的javap輸出 public class StaticDispatch {?
    public StaticDispatch();?
    Code:?
    0: aload_0?
    1: invokespecial #1 // Method java/lang/Object."<init>":?
    ()V?
    4: return?

    public void sayHello(StaticDispatch$Human);?
    Code:?
    0: getstatic #2 // Field java/lang/System.out:Ljava/?
    io/PrintStream;?
    3: ldc #3 // String human say hello?
    5:?invokevirtual?#4 // Method java/io/PrintStream.printl?
    n:(Ljava/lang/String;)V?
    8: return?

    public void sayHello(StaticDispatch$Man);?
    Code:?
    0: getstatic #2 // Field java/lang/System.out:Ljava/?
    io/PrintStream;?
    3: ldc #5 // String man say hello?
    5:?invokevirtual?#4 // Method java/io/PrintStream.printl?
    n:(Ljava/lang/String;)V?
    8: return?

    public void sayHello(StaticDispatch$Woman);?
    Code:?
    0: getstatic #2 // Field java/lang/System.out:Ljava/?
    io/PrintStream;?
    3: ldc #6 // String woman say hello?
    5:?invokevirtual?#4 // Method java/io/PrintStream.printl?
    n:(Ljava/lang/String;)V?
    8: return?

    public static void main(java.lang.String[]);?
    Code:?
    0: new #7 // class StaticDispatch$Man?
    3: dup?
    4: invokespecial #8 // Method StaticDispatch$Man."<init>?
    ":()V?
    7: astore_1?
    8: new #9 // class StaticDispatch$Woman?
    11: dup?
    12: invokespecial #10 // Method StaticDispatch$Woman."<ini?
    t>":()V?
    15: astore_2?
    16: new #11 // class StaticDispatch?
    19: dup?
    20: invokespecial #12 // Method "<init>":()V?
    23: astore_3?
    24: aload_3?
    25: aload_1?
    26: invokevirtual #13 // Method sayHello:(LStaticDispatch$?
    Human;)V?
    29: aload_3?
    30: aload_2?
    31: invokevirtual #13 // Method sayHello:(LStaticDispatch$?
    Human;)V?
    34: return?
    }

    ?

    ??? 由此,我們可以看到,其實invokevirtual? 也有可能是靜態(tài)分派的.也就是說

    寫道 invokevirtual 指令與動態(tài)分派沒有直接的聯(lián)系.但是invokespecial調(diào)用的目標必然是可以靜態(tài)綁定的

    ??

    ??? 本節(jié)的最后,必須還要說一下invokevirtual ,invokespecial指令與虛方法之間的關(guān)系.雖然invokevirtual 與方法分派沒有直接的關(guān)系,但是這兩個指令與虛方法之間還是有非常大的聯(lián)系的.

    寫道 所有invokespecial指令調(diào)用的方法都是非虛方法,而非虛方法也都是用invokespecial方法調(diào)用的.但是,后半句有兩個例外,static修飾的方法與final修飾且非private的方法 虛方法都是通過invokevirtual指令來調(diào)用的

    ????

    ??? ?上面說的兩個例外說明一下, static修飾的方法通過invokestatic 指令來調(diào)用.

    ???? 而final修飾且非private的方法也是用invokevirtual指令來調(diào)用的.這個可以看下撒迦的說明

    RednaxelaFX 寫道 直接把答案說出來就不有趣了。讓我舉個例子來誘導(dǎo)一下。?
    關(guān)鍵詞:分離編譯,二進制兼容性?

    A.java?
    Java代碼??
  • public?class?A?{??
  • ??public?void?foo()?{?/*?...?*/?}??
  • }??


  • B.java?
    Java代碼??
  • public?class?B?extends?A?{??
  • ??public?void?foo()?{?/*?...?*/?}??
  • }??


  • C.java?
    Java代碼??
  • public?class?C?extends?B?{??
  • ??public?final?void?foo()?{?/*?...?*/?}??
  • }??


  • 這樣的話有3個源碼文件,它們可以分別編譯。三個類有繼承關(guān)系,每個都有自己的foo()的實現(xiàn)。其中C.foo()是final的。?

    那么如果在別的什么地方,?
    Java代碼??
  • A?a?=?getA();??
  • a.foo();??

  • 這個a.foo()應(yīng)該使用invokevirtual是很直觀的對吧??
    而這個實際的調(diào)用目標也有可能是C.foo(),對吧??

    所以為了設(shè)計的簡單性,以及更好的二進制兼容性……(此處省略

    ?4 單分派與多分派

    ??? 首先解釋一下這兩個概念.在《Java與模式》中的譯文中提出了宗量這個概念。

    ?

    寫道 方法的接受者與方法的參數(shù)統(tǒng)稱為方法的宗量。根據(jù)分派基于多少種宗量,可以將分派劃分為單分派和多分派。

    ???? “方法的接受者”這個本文上面已經(jīng)有說明了,而“方法的參數(shù)”就是指方法的參數(shù)類型和個數(shù)。?

    ??? 其實這個定義并不好理解。我找不到其他好的例子來說明這個,所以采用《深入Java虛擬機--JVM高級特性與最佳實踐》的例子說明,包括后面的說明很多都來自此書

    ?

    Java代碼??
  • public?class?Dispatcher?{??
  • ??
  • ????static?class?QQ?{??
  • ????}??
  • ??
  • ????static?class?_360?{??
  • ????}??
  • ??
  • ????public?static?class?Father?{??
  • ????????public?void?hardChoice(QQ?qq)?{??
  • ????????????System.out.println("father?choose?qq");??
  • ????????}??
  • ??
  • ????????public?void?hardChoice(_360?_360)?{??
  • ????????????System.out.println("father?choose?360");??
  • ????????}??
  • ????}??
  • ??????
  • ????public?static?class?Son?extends?Father{??
  • ????????public?void?hardChoice(QQ?qq)?{??
  • ????????????System.out.println("son?choose?qq");??
  • ????????}??
  • ??
  • ????????public?void?hardChoice(_360?_360)?{??
  • ????????????System.out.println("son?choose?360");??
  • ????????}??
  • ????}??
  • ??
  • ????public?static?void?main(String[]?args)?{??
  • ????????Father?father?=?new?Father();??
  • ????????Father?son?=?new?Son();??
  • ????????father.hardChoice(new?_360());??
  • ????????son.hardChoice(new?QQ());??
  • ??????????
  • ????}??
  • ??
  • }??
  • ?

    ?????

    console輸出 寫道 father choose 360?
    son choose qq?

    ?

    ??? 上面的例子中 ,我們需要關(guān)心的主要是這兩行代碼

    Java代碼??
  • father.hardChoice(new?_360());??
  • son.hardChoice(new?QQ());??
  • ???? 我們分別從編譯階段和運行階段分別分析這個分派的過程。在編譯階段,jvm在選擇哪個hardChoice方法的時候有兩點依據(jù):一是靜態(tài)類型是Fatcher還是Son.二是方法參數(shù)的QQ還是360。根據(jù)這兩點,在靜態(tài)編譯的時候,這兩行代碼會被翻譯成 Father.hardChoice(360)和 Father.hardChoice(QQ).到這里,我們就可以知道,

    ?

    寫道 Java是靜態(tài)多分派的語言

    ?

    ??? 在運行階段,執(zhí)行 son.hardChoice(new QQ());?的時候,由于編譯器已經(jīng)在編譯階段決定目標方法的簽名必須是 “hardChoice(QQ)”,jvm此時不會關(guān)心傳遞過來的QQ參數(shù)到底是 “騰訊QQ”還是“奇瑞QQ”,因為這個時候參數(shù)的靜態(tài)類型,實際類型都不會對方法的分派構(gòu)成任何影響,唯一可以影響jvm進行方法分派的只有該方法的接受者,也就是son。這個時候,其實就是一個宗量作為分派的選擇,也就是

    ?

    寫道 Java是動態(tài)單分派的語言

    ?

    ?? 我想應(yīng)該很多人對靜態(tài)多分派的說明不會有疑義,而對動態(tài)單分派會有一些疑問。因為我第一次看的時候也覺得,就上面這個QQ和360的例子并不能十分好的解釋在運行期動態(tài)分派的時候,jvm只對方法的接受者敏感,而對方法的參數(shù)無視。我想大家是否有想到我在本文第一節(jié)說靜態(tài)分派的時候提到的那個問題:

    寫道 Java語言中方法重載采用靜態(tài)分派是JVM規(guī)范規(guī)定的還是語言級別的規(guī)定??

    ??? 在靜態(tài)分派的那個重載的例子中:

    Java代碼??
  • Human?man?=?new?Man();??
  • Human?woman?=?new?Woman();??
  • StaticDispatch?sd?=?new?StaticDispatch();??
  • sd.sayHello(man);??
  • sd.sayHello(woman);???
  • console輸出 寫道 human say hello?
    human say hello

    ??? 可以想想,為什么最后都會執(zhí)行 Human類的sayHello方法。這里就可以有很明確的解釋了,就是因為Java語言是動態(tài)單分派的!在編譯階段 man和woman都是Human類型,所以在運行時調(diào)用sd.sayHello(man);和 sd.sayHello(woman);的時候,jvm已經(jīng)不關(guān)心sayHello方法參數(shù)的真實類型是什么了,它只關(guān)心具體的接受者是什么。那么,結(jié)果顯而易見,他們都會調(diào)用Human類的sayHello方法。所以,

    ???

    寫道 Java語言對重載采用靜態(tài)分派的原因在于Java是動態(tài)單分派的!

    ?

    ??? 最后,摘錄下《深入Java虛擬機--JVM高級特性與最佳實踐》中關(guān)于動態(tài)分派的說明:

    寫道 今天(JDK1.6時期)的Java語言是一門靜態(tài)多分派,動態(tài)多分派的語言。強調(diào)“今天的Java語言”是因為這個結(jié)論未必會恒久不變,C#在3.0以及之前的版本與Java醫(yī)院也是動態(tài)單分派的語言,但是在C#4.0中引入dynamic類型以后,就可以方便地實現(xiàn)動態(tài)多分派。Java也已經(jīng)在JSR-292中開始規(guī)劃對動態(tài)語言的支持了,日后很可能提供類似的動態(tài)類型功能。

    ?

    ??

    最后,再次感謝撒迦與《深入Java虛擬機--JVM高級特性與最佳實踐》對本文的大力支持。哈哈?

    總結(jié)

    以上是生活随笔為你收集整理的JVM之方法调用-分派的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。