標籤:進入 obj .sh 失敗 物件導向編程 return back 應該 extend
多態一般分為兩種:重寫式多態和重載式多態。重寫和重載這兩個知識點前面的文章已經詳細將結果了,這裡就不多說了。
重載式多態,也叫編譯時間多態。也就是說這種多態再編譯時間已經確定好了。重載大家都知道,方法名相同而參數列表不同的一組方法就是重載。在調用這種重載的方法時,通過傳入不同的參數最後得到不同的結果。
但是這裡是有歧義的,有的人覺得不應該把重載也算作多態。因為很多人對多態的理解是:程式中定義的引用變數所指向的具體類型和通過該引用變數發出的方法調用在編程時並不確定,而是在程式運行期間才確定,這種情況叫做多態。 這個定義中描述的就是我們的第二種多態—重寫式多態。並且,重載式多態並不是物件導向編程特有的,而多態卻是物件導向三大特性之一(如果我說的不對,記得告訴我。。)。
我覺得大家也沒有必要在定義上去深究這些,我的理解是:同一個行為具有多個不同表現形式或形態的能力就是多態,所以我認為重載也是一種多態,如果你不同意這種觀點,我也接受。
重寫式多態,也叫運行時多態。這種多態通過動態綁定(dynamic binding)技術來實現,是指在執行期間判斷所引用對象的實際類型,根據其實際的類型調用其相應的方法。也就是說,只有程式運行起來,你才知道調用的是哪個子類的方法。
這種多態通過函數的重寫以及向上轉型來實現,我們上面代碼中的例子就是一個完整的重寫式多態。我們接下來講的所有多態都是重寫式多態,因為它才是物件導向編程中真正的多態。
向上轉型
子類引用的對象轉換為父類類型稱為向上轉型。通俗地說就是是將子類對象轉為父類對象。此處父類對象可以是介面。
看一個大家都知道的例子:
public class Animal { public void eat(){ System.out.println("animal eatting..."); }}public class Cat extends Animal{ public void eat(){ System.out.println("我吃魚"); }}public class Dog extends Animal{ public void eat(){ System.out.println("我吃骨頭"); } public void run(){ System.out.println("我會跑"); }}public class Main { public static void main(String[] args) { Animal animal = new Cat(); //向上轉型 animal.eat(); animal = new Dog(); animal.eat(); }}//結果://我吃魚//我吃骨頭
這就是向上轉型,Animal animal = new Cat();將子類對象Cat轉化為父類對象Animal。這個時候animal這個引用調用的方法是子類方法。
轉型過程中需要注意的問題
- 向上轉型時,子類單獨定義的方法會丟失。比如上面Dog類中定義的run方法,當animal引用指向Dog類執行個體時是訪問不到run方法的,
animal.run()會報錯。
- 子類引用不能指向父類對象。
Cat c = (Cat)new Animal()這樣是不行的。
向上轉型的好處
向下轉型
與向上轉型相對應的就是向下轉型了。向下轉型是把父類對象轉為子類對象。(請注意!這裡是有坑的。)
案例驅動
先看一個例子:
//還是上面的animal和cat dogAnimal a = new Cat();Cat c = ((Cat) a);c.eat();//輸出 我吃魚Dog d = ((Dog) a);d.eat();// 報錯 : java.lang.ClassCastException:com.chengfan.animal.Cat cannot be cast to com.chengfan.animal.DogAnimal a1 = new Animal();Cat c1 = ((Cat) a1);c1.eat();// 報錯 : java.lang.ClassCastException:com.chengfan.animal.Animal cannot be cast to com.chengfan.animal.Cat
為什麼第一段代碼不報錯呢?相比你也知道了,因為a本身就是Cat對象,所以它理所當然的可以向下轉型為Cat,也理所當然的不能轉為Dog,你見過一條狗突然就變成一隻貓這種操蛋現象?
而a1為Animal對象,它也不能被向下轉型為任何子類對象。比如你去考古,發現了一個新生物,知道它是一種動物,但是你不能直接說,啊,它是貓,或者說它是狗。
向下轉型注意事項
- 向下轉型的前提是父類對象指向的是子類對象(也就是說,在向下轉型之前,它得先向上轉型)
向下轉型只能轉型為本類對象(貓是不能變成狗的)。
看一個經典案例:
class A { public String show(D obj) { return ("A and D"); } public String show(A obj) { return ("A and A"); }}class B extends A{ public String show(B obj){ return ("B and B"); } public String show(A obj){ return ("B and A"); }}class C extends B{}class D extends B{}public class Demo { public static void main(String[] args) { A a1 = new A(); A a2 = new B(); B b = new B(); C c = new C(); D d = new D(); System.out.println("1--" + a1.show(b)); System.out.println("2--" + a1.show(c)); System.out.println("3--" + a1.show(d)); System.out.println("4--" + a2.show(b)); System.out.println("5--" + a2.show(c)); System.out.println("6--" + a2.show(d)); System.out.println("7--" + b.show(b)); System.out.println("8--" + b.show(c)); System.out.println("9--" + b.show(d)); }}//結果://1--A and A//2--A and A//3--A and D//4--B and A//5--B and A//6--A and D//7--B and B//8--B and B//9--A and D//能看懂這個結果嗎?先自分析一下。
前三個,強行分析,還能看得懂。但是第四個,大概你就傻了吧。為什麼不是b and b呢?
這裡就要學點新東西了。
當父類對象引用變數引用子類對象時,被引用對象的類型決定了調用誰的成員方法,引用變數類型決定可調用的方法。如果子類中沒有覆蓋該方法,那麼會去父類中尋找。
可能讀起來比較拗口,我們先來看一個簡單的例子:
class X { public void show(Y y){ System.out.println("x and y"); } public void show(){ System.out.println("only x"); }}class Y extends X { public void show(Y y){ System.out.println("y and y"); } public void show(int i){ }}class main{ public static void main(String[] args) { X x = new Y(); x.show(new Y()); x.show(); }}//結果//y and y//only x
Y繼承了X,覆蓋了X中的show(Y y)方法,但是沒有覆蓋show()方法。
這個時候,參考型別為X的x指向的對象為Y,這個時候,調用的方法由Y決定,會先從Y中尋找。執行x.show(new Y());,該方法在Y中定義了,所以執行的是Y裡面的方法;
但是執行x.show();的時候,有的人會說,Y中沒有這個方法啊?它好像是去父類中找該方法了,因為調用了X中的方法。
事實上,Y類中是有show()方法的,這個方法繼承自X,只不過沒有覆蓋該方法,所以沒有在Y中明確寫出來而已,看起來像是調用了X中的方法,實際上調用的還是Y中的。
這個時候再看上面那句難理解的話就不難理解了吧。X是引用變數類型,它決定哪些方法可以調用;show()和show(Y y)可以調用,而show(int i)不可以調用。Y是被引用對象的類型,它決定了調用誰的方法:調用y的方法。
上面的是一個簡單的知識,它還不足以讓我們理解那個複雜的例子。我們再來看這樣一個知識:
繼承鏈中對象方法的調用的優先順序:this.show(O)、super.show(O)、this.show((super)O)、super.show((super)O)。
如果你能理解這個調用關係,那麼多態你就掌握了。我們回到那個複雜的例子:
abcd的關係是這樣的:C/D —> B —> A
我們先來分析4 : a2.show(b)
- 首先,a2是類型為A的參考型別,它指向類型為B的對象。A確定可調用的方法:show(D obj)和show(A obj)。
a2.show(b) ==> this.show(b),這裡this指的是B。
- 然後.在B類中找show(B obj),找到了,可惜沒用,因為show(B obj)方法不在可調用範圍內,
this.show(O)失敗,進入下一層級:super.show(O),super指的是A。
- 在A 中尋找show(B obj),失敗,因為沒用定義這個方法。進入第三層級:
this.show((super)O),this指的是B。
- 在B中找show((A)O),找到了:show(A obj),選擇調用該方法。
- 輸出:B and A
如果你能看懂這個過程,並且能分析出其他的情況,那你就真的掌握了。
我們再來看一下9:b.show(d)
- 首先,b為類型為B的引用對象,指向類型為B的對象。沒有涉及向上轉型,只會調用本類中的方法。
- 在B中尋找show(D obj),方法。現在你不會說沒找到了吧?找到了,直接調用該方法。
- 輸出 A and D。
總結
本篇文章的內容大體上就是這些了。我們來總結一下。
- 多態,簡而言之就是同一個行為具有多個不同表現形式或形態的能力。
- 多態的分類:運行時多態和編譯時間多態。
- 運行時多態的前提:繼承(實現),重寫,向上轉型
- 向上轉型與向下轉型。
- 繼承鏈中對象方法的調用的優先順序:this.show(O)、super.show(O)、this.show((super)O)、super.show((super)O)。
Java中的多態