多態(tài)性實現(xiàn)機制——靜態(tài)分派與動態(tài)分派

方法解析

Class 文件的編譯過程中不包含傳統(tǒng)編譯中的連接步驟,一切方法調(diào)用在 Class 文件里面存儲的都只是符號引用柿菩,而不是方法在實際運行時內(nèi)存布局中的入口地址。這個特性給 Java 帶來了更強大的動態(tài)擴展能力,使得可以在類運行期間才能確定某些目標方法的直接引用,稱為動態(tài)連接尼酿,也有一部分方法的符號引用在類加載階段或第一次使用時轉(zhuǎn)化為直接引用,這種轉(zhuǎn)化稱為靜態(tài)解析植影。這在前面的“Java 內(nèi)存區(qū)域與內(nèi)存溢出”一文中有提到裳擎。

靜態(tài)解析成立的前提是:方法在程序真正執(zhí)行前就有一個可確定的調(diào)用版本,并且這個方法的調(diào)用版本在運行期是不可改變的思币。換句話說鹿响,調(diào)用目標在編譯器進行編譯時就必須確定下來羡微,這類方法的調(diào)用稱為解析。

在 Java 語言中惶我,符合“編譯器可知妈倔,運行期不可變”這個要求的方法主要有靜態(tài)方法和私有方法兩大類,前者與類型直接關(guān)聯(lián)绸贡,后者在外部不可被訪問盯蝴,這兩種方法都不可能通過繼承或別的方式重寫出其他的版本,因此它們都適合在類加載階段進行解析恃轩。

Java 虛擬機里共提供了四條方法調(diào)用字節(jié)指令结洼,分別是:

  • invokestatic:調(diào)用靜態(tài)方法。
  • invokespecial:調(diào)用實例構(gòu)造器<init style="box-sizing: border-box; padding: 0px; margin: 0px;">方法叉跛、私有方法和父類方法松忍。</init>
  • invokevirtual:調(diào)用所有的虛方法。
  • invokeinterface:調(diào)用接口方法筷厘,會在運行時再確定一個實現(xiàn)此接口的對象鸣峭。

只要能被 invokestatic 和 invokespecial 指令調(diào)用的方法,都可以在解析階段確定唯一的調(diào)用版本酥艳,符合這個條件的有靜態(tài)方法摊溶、私有方法、實例構(gòu)造器和父類方法四類充石,它們在類加載時就會把符號引用解析為該方法的直接引用莫换。這些方法可以稱為非虛方法(還包括 final 方法),與之相反骤铃,其他方法就稱為虛方法(final 方法除外)拉岁。這里要特別說明下 final 方法,雖然調(diào)用 final 方法使用的是 invokevirtual 指令惰爬,但是由于它無法覆蓋喊暖,沒有其他版本,所以也無需對方發(fā)接收者進行多態(tài)選擇撕瞧。Java 語言規(guī)范中明確說明了 final 方法是一種非虛方法陵叽。

解析調(diào)用一定是個靜態(tài)過程,在編譯期間就完全確定丛版,在類加載的解析階段就會把涉及的符號引用轉(zhuǎn)化為可確定的直接引用巩掺,不會延遲到運行期再去完成。而分派調(diào)用則可能是靜態(tài)的也可能是動態(tài)的页畦,根據(jù)分派依據(jù)的宗量數(shù)(方法的調(diào)用者和方法的參數(shù)統(tǒng)稱為方法的宗量)又可分為單分派和多分派胖替。兩類分派方式兩兩組合便構(gòu)成了靜態(tài)單分派、靜態(tài)多分派、動態(tài)單分派刊殉、動態(tài)多分派四種分派情況。

靜態(tài)分派

所有依賴靜態(tài)類型來定位方法執(zhí)行版本的分派動作州胳,都稱為靜態(tài)分派记焊,靜態(tài)分派的最典型應(yīng)用就是多態(tài)性中的方法重載。靜態(tài)分派發(fā)生在編譯階段栓撞,因此確定靜態(tài)分配的動作實際上不是由虛擬機來執(zhí)行的遍膜。下面通過一段方法重載的示例程序來更清晰地說明這種分派機制:

class Human{  
}    
class Man extends Human{  
}  
class Woman extends Human{  
}  

public class StaticPai{  

    public void say(Human hum){  
        System.out.println("I am human");  
    }  
    public void say(Man hum){  
        System.out.println("I am man");  
    }  
    public void say(Woman hum){  
        System.out.println("I am woman");  
    }  

    public static void main(String[] args){  
        Human man = new Man();  
        Human woman = new Woman();  
        StaticPai sp = new StaticPai();  
        sp.say(man);  
        sp.say(woman);  
    }  
}  

上面代碼的執(zhí)行結(jié)果如下:

 I am human
 I am human

以上結(jié)果的得出應(yīng)該不難分析。在分析為什么會選擇參數(shù)類型為 Human 的重載方法去執(zhí)行之前瓤湘,先看如下代碼:

Human man = new Man();

我們把上面代碼中的“Human”稱為變量的靜態(tài)類型瓢颅,后面的“Man”稱為變量的實際類型。靜態(tài)類型和實際類型在程序中都可以發(fā)生一些變化弛说,區(qū)別是靜態(tài)類型的變化僅僅在使用時發(fā)生挽懦,變量本身的靜態(tài)類型不會被改變,并且最終的靜態(tài)類型是在編譯期可知的木人,而實際類型變化的結(jié)果在運行期才可確定信柿。

回到上面的代碼分析中,在調(diào)用 say()方法時醒第,方法的調(diào)用者(回憶上面關(guān)于宗量的定義渔嚷,方法的調(diào)用者屬于宗量)都為 sp 的前提下,使用哪個重載版本稠曼,完全取決于傳入?yún)?shù)的數(shù)量和數(shù)據(jù)類型(方法的參數(shù)也是數(shù)據(jù)宗量)形病。代碼中刻意定義了兩個靜態(tài)類型相同、實際類型不同的變量霞幅,可見編譯器(不是虛擬機漠吻,因為如果是根據(jù)靜態(tài)類型做出的判斷,那么在編譯期就確定了)在重載時是通過參數(shù)的靜態(tài)類型而不是實際類型作為判定依據(jù)的蝗岖。并且靜態(tài)類型是編譯期可知的侥猩,所以在編譯階段,javac 編譯器就根據(jù)參數(shù)的靜態(tài)類型決定使用哪個重載版本抵赢。這就是靜態(tài)分派最典型的應(yīng)用欺劳。

動態(tài)分派

動態(tài)分派與多態(tài)性的另一個重要體現(xiàn)——方法覆寫有著很緊密的關(guān)系。向上轉(zhuǎn)型后調(diào)用子類覆寫的方法便是一個很好地說明動態(tài)分派的例子铅鲤。這種情況很常見划提,因此這里不再用示例程序進行分析。很顯然邢享,在判斷執(zhí)行父類中的方法還是子類中覆蓋的方法時鹏往,如果用靜態(tài)類型來判斷,那么無論怎么進行向上轉(zhuǎn)型骇塘,都只會調(diào)用父類中的方法伊履,但實際情況是韩容,根據(jù)對父類實例化的子類的不同,調(diào)用的是不同子類中覆寫的方法唐瀑,很明顯群凶,這里是要根據(jù)變量的實際類型來分派方法的執(zhí)行版本的。而實際類型的確定需要在程序運行時才能確定下來哄辣,這種在運行期根據(jù)實際類型確定方法執(zhí)行版本的分派過程稱為動態(tài)分派请梢。

單分派和多分派

前面給出:方法的接受者(亦即方法的調(diào)用者)與方法的參數(shù)統(tǒng)稱為方法的宗量。但分派是根據(jù)一個宗量對目標方法進行選擇力穗,多分派是根據(jù)多于一個宗量對目標方法進行選擇毅弧。

為了方便理解,下面給出一段示例代碼:

class Eat{  
}  
class Drink{  
}  

class Father{  
    public void doSomething(Eat arg){  
        System.out.println("爸爸在吃飯");  
    }  
    public void doSomething(Drink arg){  
        System.out.println("爸爸在喝水");  
    }  
}  

class Child extends Father{  
    public void doSomething(Eat arg){  
        System.out.println("兒子在吃飯");  
    }  
    public void doSomething(Drink arg){  
        System.out.println("兒子在喝水");  
    }  
}  

public class SingleDoublePai{  
    public static void main(String[] args){  
        Father father = new Father();  
        Father child = new Child();  
        father.doSomething(new Eat());  
        child.doSomething(new Drink());  
    }  
}  

運行結(jié)果應(yīng)該很容易預(yù)測到当窗,如下:

爸爸在吃飯
兒子在喝水

我們首先來看編譯階段編譯器的選擇過程够坐,即靜態(tài)分派過程。這時候選擇目標方法的依據(jù)有兩點:一是方法的接受者(即調(diào)用者)的靜態(tài)類型是 Father 還是 Child崖面,二是方法參數(shù)類型是 Eat 還是 Drink咆霜。因為是根據(jù)兩個宗量進行選擇,所以 Java 語言的靜態(tài)分派屬于多分派類型嘶朱。

再來看運行階段虛擬機的選擇蛾坯,即動態(tài)分派過程。由于編譯期已經(jīng)了確定了目標方法的參數(shù)類型(編譯期根據(jù)參數(shù)的靜態(tài)類型進行靜態(tài)分派)疏遏,因此唯一可以影響到虛擬機選擇的因素只有此方法的接受者的實際類型是 Father 還是 Child脉课。因為只有一個宗量作為選擇依據(jù),所以 Java 語言的動態(tài)分派屬于單分派類型财异。

image

根據(jù)以上論證倘零,我們可以總結(jié)如下:目前的 Java 語言(JDK1.6)是一門靜態(tài)多分派、動態(tài)單分派的語言戳寸。

原文:http://wiki.jikexueyuan.com/project/java-vm/polymorphism.html

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末呈驶,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子疫鹊,更是在濱河造成了極大的恐慌袖瞻,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,427評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件拆吆,死亡現(xiàn)場離奇詭異聋迎,居然都是意外死亡,警方通過查閱死者的電腦和手機枣耀,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評論 3 395
  • 文/潘曉璐 我一進店門霉晕,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事牺堰≈羟幔” “怎么了?”我有些...
    開封第一講書人閱讀 165,747評論 0 356
  • 文/不壞的土叔 我叫張陵伟葫,是天一觀的道長哺眯。 經(jīng)常有香客問我,道長扒俯,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,939評論 1 295
  • 正文 為了忘掉前任一疯,我火速辦了婚禮撼玄,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘墩邀。我一直安慰自己掌猛,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,955評論 6 392
  • 文/花漫 我一把揭開白布眉睹。 她就那樣靜靜地躺著荔茬,像睡著了一般。 火紅的嫁衣襯著肌膚如雪竹海。 梳的紋絲不亂的頭發(fā)上慕蔚,一...
    開封第一講書人閱讀 51,737評論 1 305
  • 那天,我揣著相機與錄音斋配,去河邊找鬼孔飒。 笑死,一個胖子當(dāng)著我的面吹牛艰争,可吹牛的內(nèi)容都是我干的坏瞄。 我是一名探鬼主播,決...
    沈念sama閱讀 40,448評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼甩卓,長吁一口氣:“原來是場噩夢啊……” “哼鸠匀!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起逾柿,我...
    開封第一講書人閱讀 39,352評論 0 276
  • 序言:老撾萬榮一對情侶失蹤缀棍,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后机错,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體睦柴,經(jīng)...
    沈念sama閱讀 45,834評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,992評論 3 338
  • 正文 我和宋清朗相戀三年毡熏,在試婚紗的時候發(fā)現(xiàn)自己被綠了坦敌。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,133評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖狱窘,靈堂內(nèi)的尸體忽然破棺而出杜顺,到底是詐尸還是另有隱情,我是刑警寧澤蘸炸,帶...
    沈念sama閱讀 35,815評論 5 346
  • 正文 年R本政府宣布躬络,位于F島的核電站,受9級特大地震影響搭儒,放射性物質(zhì)發(fā)生泄漏穷当。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,477評論 3 331
  • 文/蒙蒙 一淹禾、第九天 我趴在偏房一處隱蔽的房頂上張望馁菜。 院中可真熱鬧,春花似錦铃岔、人聲如沸汪疮。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,022評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽智嚷。三九已至,卻和暖如春纺且,著一層夾襖步出監(jiān)牢的瞬間盏道,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,147評論 1 272
  • 我被黑心中介騙來泰國打工载碌, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留摇天,地道東北人。 一個月前我還...
    沈念sama閱讀 48,398評論 3 373
  • 正文 我出身青樓恐仑,卻偏偏與公主長得像泉坐,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子裳仆,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,077評論 2 355

推薦閱讀更多精彩內(nèi)容