多多色-多人伦交性欧美在线观看-多人伦精品一区二区三区视频-多色视频-免费黄色视屏网站-免费黄色在线

國內最全IT社區平臺 聯系我們 | 收藏本站
阿里云優惠2
您當前位置:首頁 > php開源 > php教程 > Java虛擬機(九)——多態性理解

Java虛擬機(九)——多態性理解

來源:程序員人生   發布時間:2016-09-25 09:09:33 閱讀次數:2419次

介紹

??對面向對象的3大特點,很多人可以絕不猶豫地講出來封裝,繼承,多態封裝,和繼承自沒必要說,而對多態的理解,可能對很多人來講,總好像理解了,但是好像又有點迷惑,這篇文章側重介紹這個特性。

??多態的定義:指允許不同類的對象對同1消息做出響應。即同1消息可以根據發送對象的不同而采取多種不同的行動方式。這類技術稱為動態綁定(dynamic binding),是指在履行期間判斷所援用對象的實際類型,根據其實際的類型調用其相應的方法。

??現實中,關于多態的例子不勝枚舉。比方說按下 F1 鍵這個動作,如果當前在 Flash 界面下彈出的就是 AS 3 的幫助文檔;如果當前在 Word 下彈出的就是 Word 幫助;在 Windows 下彈出的就是 Windows 幫助和支持。同1個事件產生在不同的對象上會產生不同的結果。

多態的作用:消除類型之間的耦合關系,增加程序的靈活性和擴大性。

Java中多態性的體現

Java多態性主要體現在以下兩個方面。

重寫(overwrite)

子類繼承父類,重寫父類方法,注意的是方法簽名必須相同, 返回類型必須是本類或其子類的實例(jdk 1.5 版本以后)。

重載(overload )

類內部可以有很多同名的方法,注意的是名稱相同, 參數及返回值類型可以不同, 這就叫重載。

多態性的實現原理

要了解多態機制的具體實現機制,就需要深入了解Java 虛擬機對方法的調用進程和分派特性。
??首先需要明白,方法調用其實不同等于方法履行,方法調用階段唯1的任務就是肯定被調用方法的版本(即調用哪個方法),暫時還不觸及方法內部的具體運行進程。在程序運行時,進行方法調用是最普遍、最頻繁的操作,Class文件的編譯進程中不包括傳統編譯中的連接步驟,1切方法調用在Class文件里面存儲的都只是符號援用,而不是方法在實際運行時內存布局中的入口地址(相當于之前說的直接援用)。這個特性給Java帶來了更強大的動態擴大能力,但也使得Java方法調用進程變得相對復雜起來,需要在類加載期間,乃至到運行期間才能肯定目標方法的直接援用。

解析

??所有方法調用中的目標方法在Class文件里面都是1個常量池中的符號援用,在類加載的解析階段,會將其中的1部份符號援用轉化為直接援用,這類解析能成立的條件是:方法在程序真正運行之前就有1個可肯定的調用版本,并且這個方法的調用版本在運行期是不可改變的。換句話說,調用目標在程序代碼寫好、編譯器進行編譯時就必須肯定下來。這類方法的調用稱為解析(Resolution)。

??在Java語言中符合“編譯期可知,運行期不可變”這個要求的方法,主要包括靜態方法私有方法兩大類,前者與類型直接關聯,后者在外部不可被訪問,這兩種方法各自的特點決定了它們都不可能通過繼承或別的方式重寫其他版本,因此它們都合適在類加載階段進行解析

與之相對應的是,在Java虛擬機里面提供了5條方法調用字節碼指令,分別以下。

invokestatic:調用靜態方法。
invokespecial:調用實例構造器<init>方法、私有方法和父類方法。
invokevirtual:調用所有的虛方法。
invokeinterface:調用接口方法,會在運行時再肯定1個實現此接口的對象。
invokedynamic:先在運行時動態解析出調用點限定符所援用的方法,然后再履行該方法,在此之前的4條調用指令,分派邏輯是固化在Java虛擬機內部的,而invokedynamic指令的分派邏輯是由用戶所設定的引導方法決定的。

??**只要能被invokestatic和invokespecial指令調用的方法,都可以在解析階段中肯定唯1的調用版本,符合這個條件的有靜態方法、私有方法、實例構造器、父類方法**4類,它們在類加載的時候就會把符號援用解析為該方法的直接援用。這些方法可以稱為非虛方法,與之相反,其他方法稱為虛方法(除去final方法,后文會提到)。

??Java中的非虛方法除使用invokestatic、invokespecial調用的方法以外還有1種,就是被final修飾的方法。雖然final方法是使用invokevirtual指令來調用的,但是由于它沒法被覆蓋,沒有其他版本,所以也不必對方法接收者進行多態選擇,又或說多態選擇的結果肯定是唯1的。,在Java語言規范中明確說明了final方法是1種非虛方法。

??解析調用1定是個靜態的進程,在編譯期間就完全肯定,在類裝載的解析階段就會把觸及的符號援用全部轉變成可肯定的直接援用,不會延遲到運行期再去完成。而分派(Dispatch)調用則多是靜態的也多是動態的,根據分派根據的宗量數可分為單分派和多分派。這兩類分派方式的兩兩組合就構成了靜態單分派、靜態多分派、動態單分派、動態多分派4種分派組合情況。

分派

1.靜態分派

??所有依賴靜態類型來定位方法履行版本的分派動作稱為靜態分派。靜態分派的典型利用是方法重載靜態分派產生在編譯階段,因此肯定靜態分派的動作實際上不是由虛擬機來履行的。另外,編譯器雖然能肯定出方法的重載版本,但在很多情況下這個重載版本其實不是“唯1的”,常常只能肯定1個“更加適合的”版本。產生這類模糊結論的主要緣由是字面量不需要定義,所以字面量沒有顯式的靜態類型,它的靜態類型只能通過語言上的規則去理解和推斷

public class StaticDispatch { static abstract class Human { } static class Man extends Human { } static class Woman extends Human { } public void sayHello(Human guy) { System.out.println("Hello guy!"); } public void sayHello(Man guy) { System.out.println("Hello gentleMan!"); } public void sayHello(Woman guy) { System.out.println("Hello lady!"); } public static void main(String[] args) { Human man = new Man(); Human woman = new Woman(); StaticDispatch sd = new StaticDispatch(); sd.sayHello(man); sd.sayHello(woman); } }

運行結果

Hello guy! Hello guy!

進程分析:

??我們把上面代碼中的“Human”稱為變量的靜態類型(Static Type),或叫做的外觀類型(Apparent Type),后面的“Man”則稱為變量的實際類型(Actual Type),靜態類型和實際類型在程序中都可以產生1些變化,區分是靜態類型的變化僅僅在使用時產生,變量本身的靜態類型不會被改變,并且終究的靜態類型是在編譯期可知的;而實際類型變化的結果在運行期才可肯定,編譯器在編譯程序的時候其實不知道1個對象的實際類型是甚么
main()里面的兩次sayHello()方法調用,在方法接收者已肯定是對象“sd”的條件下,使用哪一個重載版本,就完全取決于傳入參數的數量和數據類型。代碼中刻意地定義了兩個靜態類型相同但實際類型不同的變量,但虛擬機(準確地說是編譯器)在重載時是通過參數的靜態類型而不是實際類型作為判定根據的。并且靜態類型是編譯期可知的,因此,在編譯階段,Javac編譯器會根據參數的靜態類型決定使用哪一個重載版本,所以選擇了sayHello(Human)作為調用目標,并把這個方法的符號援用寫到main()方法里的兩條invokevirtual指令的參數中。

動態分派

我們把運行期根據實際類型肯定方法履行版本的分派進程稱為動態分派,動態分派的典型利用是方法重寫。

/** * 動態分派 * * @author bridge */ 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"); } } public static void main(String[] args) { Human man = new Man(); Human woman = new Woman(); man.sayHello(); woman.sayHello(); man = new Woman(); man.sayHello(); } }

運行結果:

Man say Hello Woman say Hello Woman say Hello

利用 javap -c 命令反編class文件,可以得到Main方法的字節碼以下:

public static void main(java.lang.String[]); Code: 0: new #2 // class methodInvoke/DynamicDispatch$Man 3: dup 4: invokespecial #3 // Method methodInvoke/DynamicDispatch$Man."<init>":()V 7: astore_1 8: new #4 // class methodInvoke/DynamicDispatch$Woman 11: dup 12: invokespecial #5 // Method methodInvoke/DynamicDispatch$Woman."<init>":()V 15: astore_2 16: aload_1 17: invokevirtual #6 // Method methodInvoke/DynamicDispatch$Human.sayHello:()V 20: aload_2 21: invokevirtual #6 // Method methodInvoke/DynamicDispatch$Human.sayHello:()V 24: new #4 // class methodInvoke/DynamicDispatch$Woman 27: dup 28: invokespecial #5 // Method methodInvoke/DynamicDispatch$Woman."<init>":()V 31: astore_1 32: aload_1 33: invokevirtual #6 // Method methodInvoke/DynamicDispatch$Human.sayHello:()V 36: return

字節碼分析:

0~15行的字節碼是準備動作,作用是建立man和woman的內存空間、調用Man和Woman類型的實例構造器,將這兩個實例的援用寄存在第1、2個局部變量表Slot當中,這個動作也就對應了代碼中的這兩句:

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

接下來的16~21句是關鍵部份,16、20兩句分別把剛剛創建的兩個對象的援用壓到棧頂,這兩個對象是將要履行的sayHello()方法的所有者,稱為接收者(Receiver)

17和21句是方法調用指令,這兩條調用指令單從字節碼角度來看,不管是指令(都是invokevirtual)還是參數(都是常量池中第22項的常量,注釋顯示了這個常量是Human.sayHello()的符號援用)完全1樣的,但是這兩句指令終究履行的目標方法其實不相同。緣由就需要從invokevirtual指令的多態查找進程開始說起,invokevirtual指令的運行時解析進程大致分為以下幾個步驟:


1)找到操作數棧頂的第1個元素所指向的對象的實際類型,記作C。

2)如果在類型C中找到與常量中的描寫符和簡單名稱都符合的方法,則進行訪問權限校驗,如果通過則返回這個方法的直接援用,查找進程結束;如果不通過,則返回java.lang.IllegalAccessError異常。

3)否則,依照繼承關系從下往上順次對C的各個父類進行第2步的搜索和驗證進程。

4)如果始終沒有找到適合的方法,則拋出java.lang.AbstractMethodError異常。

??由于invokevirtual指令履行的第1步就是在運行期肯定接收者的實際類型,所以兩次調用中的invokevirtual指令把常量池中的類方法符號援用解析到了不同的直接援用上,這個進程就是Java語言中方法重寫的本質

單分派和多分派

??方法的接收者與方法的參數統稱為方法的宗量,根據分派基于多少種宗量,可以將分派劃分為單分派多分派兩種。
單分派是根據1個宗量對目標方法進行選擇,多分派則是根據多于1個宗量對目標方法進行選擇。

/** * 單分派與多分派演示 * * @author bridge */ public class Dispatch { static class QQ { } static class _360 { } public static class Father { public void hardChoice(QQ arg) { System.out.println("Father choose QQ"); } public void hardChoice(_360 arg) { System.out.println("Father choose 360"); } } public static class Son extends Father { public void hardChoice(QQ arg) { System.out.println("Son choose QQ"); } public void hardChoice(_360 arg) { 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()); } }

運行結果

Father choose 360
Son choose QQ

Main 方法的字節碼以下:

public class methodInvoke.Dispatch { public methodInvoke.Dispatch(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return public static void main(java.lang.String[]); Code: 0: new #2 // class methodInvoke/Dispatch$Father 3: dup 4: invokespecial #3 // Method methodInvoke/Dispatch$Father."<init>":()V 7: astore_1 8: new #4 // class methodInvoke/Dispatch$Son 11: dup 12: invokespecial #5 // Method methodInvoke/Dispatch$Son."<init>":()V 15: astore_2 16: aload_1 17: new #6 // class methodInvoke/Dispatch$_360 20: dup 21: invokespecial #7 // Method methodInvoke/Dispatch$_360."<init>":()V 24: invokevirtual #8 // Method methodInvoke/Dispatch$Father.hardChoice:(LmethodInvoke/Dispatch$_360;)V 27: aload_2 28: new #9 // class methodInvoke/Dispatch$QQ 31: dup 32: invokespecial #10 // Method methodInvoke/Dispatch$QQ."<init>":()V 35: invokevirtual #11 // Method methodInvoke/Dispatch$Father.hardChoice:(LmethodInvoke/Dispatch$QQ;)V 38: return }

分析:
在main函數中調用了兩次hardChoice()方法,這兩次hardChoice()方法的選擇結果在程序輸出中已顯示得很清楚了。

我們來看看編譯階段編譯器的選擇進程,也就是靜態分派的進程。這時候選擇目標方法的根據有兩點:1是靜態類型是Father還是Son,2是方法參數是QQ還是360。這次選擇結果的終究產物是產生了兩條invokevirtual指令,兩條指令的參數分別為常量池中指向Father.hardChoice(360)及Father.hardChoice(QQ)方法的符號援用。由于是根據兩個宗量進行選擇,所以Java語言的靜態分派屬于多分派類型

再看看運行階段虛擬機的選擇,也就是動態分派的進程。在履行“son.hardChoice(new QQ())”這句代碼時,更準確地說,是在履行這句代碼所對應的invokevirtual指令時,由于編譯期已決定目標方法的簽名必須為hardChoice(QQ),虛擬機此時不會關心傳遞過來的參數“QQ”究竟是“騰訊QQ”還是“奇瑞QQ”,由于這時候參數的靜態類型、實際類型都對方法的選擇不會構成任何影響,唯1可以影響虛擬機選擇的因素只有此方法的接受者的實際類型是Father還是Son。由于只有1個宗量作為選擇根據,所以Java語言的動態分派屬于單分派類型

根據上述論證的結果,我們可以總結1句:到目前為止,Java語言是1門靜態多分派、動態單分派的語言。


參考資料

【深入理解Java虛擬機】 周志明著

生活不易,碼農辛苦
如果您覺得本網站對您的學習有所幫助,可以手機掃描二維碼進行捐贈
程序員人生
------分隔線----------------------------
分享到:
------分隔線----------------------------
關閉
程序員人生
主站蜘蛛池模板: 国产一区二区三区在线观看视频 | 色操网 | 国产好大好爽久久久久久久 | 97理论三级九七午夜在线观看 | 欧美性受xxxx喷水大胸 | 亚洲韩国日本欧美一区二区三区 | 精品日韩在线视频一区二区三区 | 自拍亚洲欧美 | 无国产精品白浆免费视 | 午夜啪啪网站 | 亚洲综合在线网 | 激情爱爱网站 | 中文字幕在线观看亚洲 | 欧美视频一 | 国产精品久久永久免费 | 精品videosex性欧美 | 中文字幕第一页在线视频 | 欧美1级 | 农村女人的一级毛片 | 手机福利视频一区二区 | 国产精品久久成人影院 | 性欧美高清 | 亚洲乱码一区二区三区在线观看 | 日毛片 | 亚洲欧洲综合 | 性欧美video超清 | 波多野结衣在线免费观看视频 | 国产福利一区二区 | 日本人视频-jlzz jlzz jlzz | 成人国产一区二区三区精品 | 羞羞网站在线观看 | freesexvideos性大全性亚洲 | 亚洲精品免费视频 | 国产欧美日韩中文久久 | 国产一区二区三区不卡观 | 国产亚洲欧美日韩国产片 | 手机在线视频观看 | 最近中文字幕大全 | 亚洲国产欧美精品 | 国产精品成人一区二区三区 | 亚洲精品欧美精品国产精品 |