Java多態性的“飄渺之旅” 原

朵星人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類中繼續尋找。

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章