一、什么是多態?
用一句話概括:事物在運行過程中存在不同的狀態。先以教科書舉例說明,下文再舉一個花木蘭替父從軍的例子加以解釋和說明,多態的存在有三個前提:
1、需要有繼承關系
2、子類重寫父類的方法
3、父類引用指向子對,
但是,其中又有很多細節需要注意。首先我們定義兩個類,一個父類Animal,一個子類Cat
父類Animal:
1 package com.wangjun.ThinkingInJava.java_14.another; 2 3 public class Animal { 4 int num=10; 5 static int age=20; 6 public void eat(){ 7 System.out.println("動物吃飯"); 8 } 9 public static void sleep(){ 10 System.out.println("動物在睡覺"); 11 } 12 public void run(){ 13 System.out.println("動物在奔跑"); 14 } 15 16 }
子類Cat
1 package com.wangjun.ThinkingInJava.java_14.another; 2 3 public class Cat extends Animal { 4 int num=80; 5 static int age=90; 6 String name="tomCat"; 7 public void eat(){ 8 System.out.println("貓吃飯"); 9 } 10 public static void sleep(){ 11 System.out.println("貓在睡覺"); 12 } 13 public void catchMouse(){ 14 System.out.println("貓在抓老鼠"); 15 } 16 17 }
測試類Demo:
1 package com.wangjun.ThinkingInJava.java_14.another; 2 3 public class Demo { 4 5 public static void main(String[] args) { 6 Animal am=new Cat(); 7 am.eat(); 8 am.sleep(); 9 am.run(); 10 //am.catchMouse(); 11 System.out.println(am.num); 12 System.out.println(am.age); 13 } 14 15 }
解釋:以上的三段代碼充分體現了多態的三個前提,即:
1、存在繼承關系:Cat類繼承了Animal類。
2、子類要重寫父類的方法:子類重寫(override)了父類的兩個成員方法eat(),sleep()。其中eat()是非靜態的,sleep()是靜態的(static)。
3、父類數據類型的引用指向了子類對象:Animal am=new Cat(),語句在堆內存中開辟了子類Cat的對象,並把棧內存中的父類(Animal)的引用指向了這個Cat對象。
到此,滿足了Java多態的必要三個前提。
二、更深層次的研究
如果再深究一點呢,我們可以看看上面測試類的輸出結果,或許對多態會有更深層次的認識。下面是執行結果:
1 貓吃飯 2 動物在睡覺 3 動物在奔跑 4 10 5 20
可以看出來,子類cat重寫了父類Animal的非靜態成員方法am.eat()的輸出結果為:貓吃飯
子類cat重寫了父類Animal的靜態成員方法am.sleep()的輸出結果為:動物在睡覺
未被子類Cat重寫的父類Animal方法am.run()的輸出結果為:動物在奔跑
System.out.println(am.num);//輸出結果為10
System.out.println(am.age);//輸出結果為20
基於以上情況,可以總結出多態成員訪問的特點:
成員變量
編譯看左邊(父類),運行看左邊(父類)
成員方法
編譯看左邊(父類),運行看右邊(子類),實現動態綁定
靜態方法
編譯看左邊(父類),運行看左邊(父類)
靜態和類相關,算不上重寫,所以訪問還是左邊的,只有非靜態成員的方法,編譯看左邊運行看右邊
三、多態的缺點
多態不能使用子類特有的屬性和方法。往上面的代碼看,子類Cat有一個特有的屬性String name=“tomCat”,並且還有一個抓老鼠的方法catchMouse()
但是在測試類中我們嘗試調用子類特有的方法catchMouse()和打印子類特有的成員屬性String name="tomCat",就會報錯。
am.catchMouse();
System.out.println(am.name);
原因就是多態的弊端:不能使用子類特有的成員屬性和子類特有的成員方法。
如果在代碼執行過程中還想使用Cat類中特有的屬性String name和它特有的成員方法catchMouse(),怎么辦呢?那我們就可以把這個父類引用指向了子類對象的am再強制變回cat類型。這樣am就是Cat類型的引用了,指向的也是Cat對象了,自然也能使用Cat類的一切屬性和一切的成員方法了。
1 public class Demo_Test { 2 3 public static void main(String[] args) { 4 Animal am=new Cat(); 5 am.eat(); 6 am.sleep(); 7 am.run(); 8 System.out.println(am.num); 9 System.out.println(am.age); 10 System.out.println("-------------------"); 11 12 Cat cat=(Cat)am; 13 cat.eat(); 14 cat.sleep(); 15 cat.run(); 16 cat.catchMouse(); 17 18 }
顯示結果:
1 貓吃飯 2 動物在睡覺 3 動物在奔跑 4 10 5 20 6 ------------------- 7 貓吃飯 8 貓在睡覺 9 動物在奔跑 10 貓在抓老鼠
很明顯,執行強轉換語句Cat cat=(Cat) am之后,cat就指向最開始在堆內存中創建的那個Cat類型的對象了。這就是多態的魅力吧,雖然它有缺點,但是它確實十分靈活,減少多余對象的創建,不是說為了使用子類的某個方法又去重新再堆內存找那個開辟一個新的子類對象。以上。。。
四、花木蘭替父從軍形象化多態
花木蘭替父親花弧從軍。那么這時候花木蘭是子類,花弧是父類。花弧有自己的成員屬性年齡、姓名、性別。花木蘭也有這些屬性,但是很明顯二者是屬性完全不一樣。花弧有自己的非靜態成員方法:騎馬殺敵,同樣花木蘭也遺傳了父親一樣的方法騎馬殺敵。花弧還有一個靜態方法自我介紹,每個人都可以問花弧姓甚名誰同時花木蘭還有一個自己特有的非靜態成員方法‘塗脂抹粉’。但是,現在花木蘭替父從軍,女扮男裝。這時候相當於父類的引用(花弧這個名字)指向了子類對象(花木蘭這個人),那么在其他類(其他的人)中訪問子類對象(花木蘭這個人)的成員屬性(姓名,年齡,性別)時,其實看到的都是花木蘭她父親的名字(花弧)、年齡(60歲)、性別(男)。當訪問子類對象(花木蘭這個人)的非靜態成員方法(騎馬打仗)時,其實都是看到花木蘭自己運用十八般武藝在騎馬打仗。當訪問花木蘭的靜態方法時(自我介紹),花木蘭自己都是用她父親的名字信息在向別人作自我介紹。並且這時候花木蘭不能使用自己特有的成員方法‘塗脂抹粉’。-----多態中的向上轉型
那么終於一將功成萬骨枯,打仗旗開得勝了,花木蘭告別了戰爭生活。有一天,遇到了自己心愛的男人,這時候愛情的力量將父類對象的引用(花弧這個名字)強制轉換為子類對象本來的引用(花木蘭這個名字),那么花木蘭又從新成為了她自己,這時候她完全是她自己了。名字是花木蘭,年齡是28,性別是女,打仗依然那樣生猛女漢子,自我介紹則堂堂正正地告訴別人我叫花木蘭。OMG!終於,終於可以使用自己特有的成員方法‘塗脂抹粉’了。從此,花木蘭完全回到了替父從軍前的那個花木蘭了。並且和自己心愛的男人幸福的過完了一生。-----多態中的向下轉型
----------------------------------------------------------華麗的分割線---------------------------------------------------------------
大家記得哈,向上轉型向下轉型一定是在多態這個前提下哈,否則強制將女兒變成父親,或者將父親變成女人,就變成東方不敗了,系統此時就會報錯非法類型轉換。哈哈哈哈哈。另外開發中一般是利用多態聲明形式參數,並將創建子類的匿名對象作為實際參數。以上。