朵星人A:人類,是一個很奇妙的物種。
朵星人B:他們好像分爲兩種,嗯 先生,以及美女?
朵星人C:對,更年輕的有叫 美少女的。
朵星人D:他們之間怎麼打招呼的?我們問問AI(編譯器大佬)吧。。
朵星人A:可以有。啓動吧~
第一次啓動:
/**
* 編譯時多態
*
* @author Sven Augustus
*/
public class StaticTest {
static abstract class Human {
}
static class Man extends StaticTest.Human {
}
static class Woman extends StaticTest.Human {
}
static class Girl extends StaticTest.Woman {
}
public void sayHello(Object guy) {
System.out.println("你...");
}
public void sayHello(Human guy) {
System.out.println("你好");
}
public void sayHello(Man guy) {
System.out.println("您好,先生");
}
public void sayHello(Woman guy) {
System.out.println("您好,美女");
}
public void sayHello(Girl guy) {
System.out.println("您好,美少女");
}
public static void main(String[] args) {
StaticTest test = new StaticTest();
StaticTest.Human manAsGuy = new StaticTest.Man();
StaticTest.Human womanAsGuy = new StaticTest.Woman();
StaticTest.Woman girlAsWoman = new StaticTest.Girl();
test.sayHello(manAsGuy);
test.sayHello(womanAsGuy);
test.sayHello(girlAsWoman);
}
}
輸出:
編譯器大佬告訴了他們答案。
朵星人衆人:納尼,他們叫的好奇怪啊?好沒禮貌啊,沒“您”。爲毛呢??還有最後一個明明是美少女,你怎麼叫美女啊?!
編譯器大佬:你們好意思嗎,你們都標爲人類或美女。
我怎麼知道他們具體是先生還是美女,亦或是美少女啊!!!所以我就只知道這麼多。
StaticTest.Human manAsGuy = new StaticTest.Man();
test.sayHello(manAsGuy);
從這裏,Human 稱爲 聲明類型(也有叫靜態類型) ,Man 稱爲 實際類型。
很清楚,現在關鍵在於 manAsGuy 作爲 參數。
在編譯階段,編譯器就可以根據 參數 的 聲明類型(或叫靜態類型) 決定使用哪個重載版本的方法。
朵星人A:說的好有道理,我們讓他們自己稱呼吧。
朵星人B:可以有。
朵星人C:贊同。
朵星人D:不會有問題吧?
朵星人A:不會的。就這樣吧~~~
第二次啓動:
/**
* 運行時多態
*
* @author Sven Augustus
*/
public class DynamicTest {
static abstract class Human {
public void sayHello() {
System.out.println("你好");
}
}
static class Man extends DynamicTest.Human {
public void sayHello() {
System.out.println("您好,我是Y先生");
}
}
static class Woman extends DynamicTest.Human {
public void sayHello() {
System.out.println("您好,我是X美女");
}
}
public static void main(String[] args) {
DynamicTest.Human manAsGuy = new DynamicTest.Man();// 註釋1
DynamicTest.Human womanAsGuy = new DynamicTest.Woman();
manAsGuy.sayHello();
womanAsGuy.sayHello();
}
}
輸出:
編譯器大佬好像去休息了,交給社會JVM回答問題。
DynamicTest.Human manAsGuy = new DynamicTest.Man();// 註釋1
manAsGuy.sayHello();
這裏與上個例子不同的是,manAsGuy不作爲 參數,是作爲引用變量 去 調用方法。
這時候,編譯器只知道 引用變量manAsGuy的 靜態類型,對於實際類型 就無能爲力。因此在運行時由JVM方法表動態綁定。
我們發現,
引用變量調用方法 的時候,決定去調用哪個方法,是由 實際類型 在運行時確認調用哪個方法,而不是 聲明類型(或叫靜態類型)。
呵呵,當然這個解釋還是比較勉強。我們繼續。
朵星人A:咦,他們太客氣了,都“您好”,還首先介紹自己,好不矜持啊。
朵星人B:地球人是這樣的嗎??
朵星人C:是這樣的。他們不知道對方是誰,只知道自己是誰的時候是這樣的。
朵星人D:好像不是啊。
朵星人A:那你說是怎樣的?
朵星人D:他們需要知道對方是誰啊!
朵星人B:有道理、
朵星人C:贊同。
朵星人A:就這樣吧~~~
第三次啓動:
/**
* 編譯時多態 和 運行時多態 混合測試
*
* @author Sven Augustus
*/
public class MixTest {
static class Human {
public String sayHello(MixTest.Human human) {
return "你好";
}
public String sayHello(MixTest.Man human) {
return "您好,先生";
}
public String sayHello(MixTest.Woman human) {
return "您好,美女";
}
/*public String sayHello(MixTest.Girl human) {
return "您好,美少女";
}*/
}
static class Man extends MixTest.Human {
public String sayHello(MixTest.Human human) {
return "你好,我是Y先生";
}
public String sayHello(MixTest.Woman human) {
return "您好,美女,我是Y先生";
}
public String sayHello(MixTest.Girl human) {
return "您好,美少女,我是Y先生";
}
// 先生對先生比較謹慎,沒那麼快介紹自己 =。=
}
static class Woman extends MixTest.Human {
public String sayHello(MixTest.Human human) {
return "你好,我是X美女";
}
public String sayHello(MixTest.Woman human) {
return "您好,美女,我是X美女";
}
public String sayHello(MixTest.Girl human) {
return "您好,美少女,我是X美女";
}
// 美女對先生比較含蓄,沒那麼快介紹自己 =。=
}
static class Girl extends MixTest.Woman {
public String sayHello(MixTest.Human human) {
return "你好,我是O美少女";
}
}
public static void main(String[] args) {
MixTest test = new MixTest();
MixTest.Human guy = new MixTest.Human();
MixTest.Human manAsGuy = new MixTest.Man();
MixTest.Man man = new MixTest.Man();
MixTest.Human womanAsGuy = new MixTest.Woman();
MixTest.Woman woman = new MixTest.Woman();
MixTest.Girl girl = new MixTest.Girl();
System.out.print("假設大家在QQ等聊天軟件上認識,這時候一般來招呼如下");
System.out.println("當然先生對先生比較謹慎,沒那麼快介紹自己:");
printMessage("一個人 歡迎 一個人", guy.sayHello(guy),
"[我不想你知道我的性別,我也不知道你的性別,囧]");
printMessage("一個人 歡迎 一名先生", guy.sayHello(man),
"[我不想你知道我的性別,我知道你是一名先生,嘿嘿]");
printMessage("一個人 歡迎 一名美女", guy.sayHello(woman),
"[我不想你知道我的性別,我知道你是一名美女,哈哈]");
printMessage("一個人[其實是先生] 歡迎 一個人", manAsGuy.sayHello(guy),
"[我不想你知道我的性別,但是你知道我是先生,可是我不知道你的性別,汗]");
printMessage("一個人[其實是先生] 歡迎 一個人[其實是先生]", manAsGuy.sayHello(manAsGuy),
"[我不想你知道我的性別,但是你知道我是先生,可我不知道你的性別(或許你是一名先生),呵]");
printMessage("一個人[其實是先生] 歡迎 一個人[其實是美女]", manAsGuy.sayHello(womanAsGuy),
"[我不想你知道我的性別,但是你知道我是先生,可我不知道你的性別(或許你是一名美女),嘿]");
printMessage("一個人[其實是先生] 歡迎 一名先生", manAsGuy.sayHello(man),
"[我不想你知道我的性別,但是你知道我是先生,我知道你也是一名先生,呵呵]");
printMessage("一個人[其實是先生] 歡迎 一名美女", manAsGuy.sayHello(woman),
"[我不想你知道我的性別,但是你知道我是先生,我知道你是一名美女,噢噢]");
printMessage("一個人[其實是先生] 歡迎 一名美少女", manAsGuy.sayHello(girl),
"[我不想你知道我的性別,但是你知道我是先生,我知道你是一名美少女,噢]");
printMessage("一名先生 歡迎 一個人 ", man.sayHello(guy),
"[我是一名光明磊落的先生,可我不知道你的性別,額]");
printMessage("一名先生 歡迎 一個人[其實是先生]", man.sayHello(manAsGuy),
"[我是一名光明磊落的先生,可我不知道你的性別(或許你是一名先生),咦]");
printMessage("一名先生 歡迎 一個人[其實是美女]", man.sayHello(womanAsGuy),
"[我是一名光明磊落的先生,可我不知道你的性別(或許你是一名美女),嗯]");
printMessage("一名先生 歡迎 一名先生", man.sayHello(man),
"[我是一名光明磊落的先生,我知道你也是一名先生,非常好,我先觀察]");
printMessage("一名先生 歡迎 一名美女", man.sayHello(woman),
"[我是一名光明磊落的先生,我知道你是一名美女,我先介紹自己]");
printMessage("一名先生 歡迎 一名美少女", man.sayHello(girl),
"[我是一名光明磊落的先生,我知道你是一名美少女,我先禮貌介紹自己]");
}
private static volatile int index = 1;
private static void printMessage(String title, String message, String narrator) {
System.out.println((index++) + "、" + String.format("%-35s%-20s%s",
new String[]{title, message, narrator}));
}
}
輸出:
社會JVM一片混沌,不知所云,亂出答案。
朵星人A:看不懂人類的世界,太複雜了吧。
朵星人B:地球人是這樣的嗎??
朵星人C:是這樣的。他們百變。
朵星人D:額。讓人類自己解讀吧。
現在 這個例子 混雜了 編譯時多態 和 運行時多態。
因此,我們首先觀察一下,發現:
a、結果 1-3中,是 單純的編譯時多態。
b、結果 4-8 對比 10-14中,“一個人[其實是先生]” 和 “ 一名先生 ”( 引用變量) 在歡迎(方法調用) 同一個類型的人(同一靜態類型參數)的時候,歡迎語是一致(調用的具體方法可能一致的?)。
c、結果9 對比 15 中,我們發現結論 b 不生效了。爲什麼呢?我們發現 一個人[其實是先生]” 和 “ 一名先生 ”還是有區別的。
我們仔細觀察一下代碼實現。
Human類有 對 Human、Man、Woman的歡迎方法
Man類有 對 Human、Woman、Girl的歡迎方法
結果9:
MixTest.Human manAsGuy = new MixTest.Man();
manAsGuy.sayHello(girl),
因爲manAsGuy 聲明是Human 類,方法從Human類開始搜索,Human類沒有歡迎Girl的方法,
因此按照最適合方法版本,兼容找到了Human 類的歡迎Woman的方法,
又因爲實際類型是Man類,該方法有重寫,因此實際執行了Man類的歡迎Woman的方法。
首先定義聲明類型 與 實際類型 存在向上轉型的情況,稱之爲“動態綁定”。
如 Parent p = new Children();
我們得出了一個
方法調用步驟:
1、編譯器檢查引用對象的聲明類型、方法名;
假設我們調用x.func(args) 方法,如果x聲明爲X類,那麼編譯器會列舉X類所有名稱爲func的方法,以及從X類的超類繼承的所有名稱爲func的方法。
2、接下來,編譯器檢查方法提供中的參數類型
如果在第1步中列舉的所有func方法中找到一個 參數類型 與 args的聲明類型 最爲匹配的,
如果方法調用,不是動態綁定,編譯器就確定調用 該func(args)方法。
如果方法調用,是動態綁定。那麼繼續下一步。
--------------------------------以下動態綁定-------------------------------------------
3、當程序運行並且使用動態綁定調用方法時,JVM會調用x對象實際類型相匹配的方法版本。
意思就是,如果 X x= new T();實際類型是T類,那麼如果T類有定義了與第2步方法簽名一致的func(args)方法,也就是重寫,那麼T類的該func(args)方法會被JVM實際調用,否則就在T類的超類X類中繼續尋找。