多態的條件
繼承。
在多態中必須存在有繼承關系的子類和父類。
重寫。
子類對父類中某些方法進行重新定義,在調用這些方法時就會調用子類的方法。
向上轉型。
在多態中需要將子類的引用賦給父類對象,只有這樣該引用才能夠具備技能調用父類的方法和子類的方法
繼承也可以替換為實現接口。
向上轉型
子類引用的對象轉換為父類類型稱為向上轉型。通俗地說就是是將子類對象轉為父類對象。此處父類對象可以是接口。
案例驅動
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()這樣是不行的。
向上轉型的好處
減少重復代碼,使代碼變得簡潔。
提高系統擴展性。 舉個例子:比如我現在有很多種類的動物,要喂它們吃東西。如果不用向上轉型,那我需要這樣寫:
public void eat(Cat c){ c.eat(); } public void eat(Dog d){ d.eat(); } //...... eat(new Cat()); eat(new Cat()); eat(new Dog()); //......
一種動物寫一個方法,如果我有一萬種動物,我就要寫一萬個方法,寫完大概猴年馬月都過了好幾個了吧。好吧,你很厲害,你耐着性子寫完了,以為可以放松一會了,突然又來了一種新的動物,你是不是又要單獨為它寫一個eat方法?開心了么?
那如果我使用向上轉型呢?我只需要這樣寫:
public void eat(Animal a){ a.eat(); } eat(new Cat()); eat(new Cat()); eat(new Dog()); //.....
恩,搞定了。代碼是不是簡潔了許多?而且這個時候,如果我又有一種新的動物加進來,我只需要實現它自己的類,讓他繼承Animal就可以了,而不需要為它單獨寫一個eat方法。是不是提高了擴展性?
向下轉型
與向上轉型相對應的就是向下轉型了。向下轉型是把父類對象轉為子類對象。(請注意!這里是有坑的。)
案例驅動
//還是上面的animal和cat dog Animal 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.Dog Animal a1 = new Animal(); Cat c1 = ((Cat) a1); c1.eat(); // 報錯 : java.lang.ClassCastException:com.chengfan.animal.Animal cannot be cast to com.chengfan.animal.Cat
向下轉型注意事項
下轉型的前提是父類對象指向的是子類對象(也就是說,在向下轉型之前,它得先向上轉型)
向下轉型只能轉型為本類對象(貓是不能變成狗的)。
大概你會說,我特么有病啊,我先向上轉型再向下轉型?? 我們回到上面的問題:喂動物吃飯,吃了飯做點什么呢?不同的動物肯定做不同的事,怎么做呢?
public void eat(Animal a){ if(a instanceof Dog){ Dog d = (Dog)a; d.eat(); d.run();//狗有一個跑的方法 } if(a instanceof Cat){ Cat c = (Cat)a; c.eat(); System.out.println("我也想跑,但是不會"); //貓會抱怨 } a.eat();//其他動物只會吃 } eat(new Cat()); eat(new Cat()); eat(new Dog()); //.....
經典案例分析多態
基本的多態和轉型我們都會了,最后加點餐。看一個經典案例:.
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
//能看懂這個結果么?先自分析一下。
當父類對象引用變量引用子類對象時,被引用對象的類型決定了調用誰的成員方法,引用變量類型決定可調用的方法。如果子類中沒有覆蓋該方法,那么會去父類中尋找。
可能讀起來比較拗口,我們先來看一個簡單的例子:
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。
