Java多型——向上轉型與向下轉型
Java的轉型之前一直處於似是而非的狀態。
其中尤其是向上轉型,大概意思誰不懂啊,A a = new B() 嘛,然而往細了摳就有點矇蔽,尤其是面對考卷裡出現的那種賊JB噁心的多型題,簡直要哭了。
翻閱了幾篇大佬們寫的部落格,整理一下思路總結如下。
首先先從最基本的解起:
向上轉型即A a = new B() 也就是父類的引用指向了子類的物件
A a;//建立父類A物件的引用a
a = new B()//new了一個子類物件B被父類的引用a所指向
這也就是向上轉型,此時a指向了一個子類的物件
由於父類引用a指向了子類的物件B,因此可以有B b = (B)a,因為a已經指向了子類所以可以強轉成B
但如果引用a指向的是正常的父類物件A,也就是 A a = new A();那麼對於B b = (B) a;會執行出錯;
這是因為對於第一個例子而言子類的引用當然可以指向子類的物件,而對於第二個例子而言子類的物件妄圖去指向父類的物件,越俎代庖?
有了最基本的轉型的概念,下面舉一個向上轉型的小例子:
public class Animal { public void eat(){ System.out.println("animal eatting..."); } } class Bird extends Animal{ public void eat(){ System.out.println("bird eatting..."); } public void fly(){ System.out.println("bird flying..."); } } class Main{ public static void main(String[] args) { Animal b=new Bird(); //向上轉型 b.eat(); //! error: b.fly(); b雖指向子類物件,但此時丟失fly()方法 } }
結果是bird eatting...
這裡子類Birl繼承了父類Animal方法同時重寫了父類的方法eat(),main方法裡父類的引用b指向了子類bird物件因此呼叫的eat方法是子類的eat方法
而fly方法是子類特有的方法,對應變數b而言由於是父類的引用並不認識子類特有的方法,所以b.fly會報錯
簡而言之 b實際指向的是子類,因此會呼叫子類本身的方法。同時向上轉型時引用變數b會遺失除與父類物件共有的其他方法,因此fly()不為b所識別。
上面舉個向上轉型的小例子幫助瞭解,那麼在實際專案中有什麼用處呢?
個人感覺提高了程式碼的複用性,舉個小例子;
public class Human { public void sleep() { System.out.println("Human sleep.."); } } class Male extends Human { @Override public void sleep() { System.out.println("Male sleep.."); } } class Female extends Human { @Override public void sleep() { System.out.println("Female sleep.."); } }
class Main{
public static void main(String[] args) {
dosleep(new Male()); //傳入的引數是子類
dosleep(new Female());
}
public static void dosleep(Human h) { //方法裡的引數是父類
h.sleep();
}
}
結果:Male sleep…
Female sleep…
為什麼說傳入的引數是子類的物件而方法裡的引數是父類引用呢,是因為只有這樣才是向上轉型所對應的
Human h = new Male(); Human h = new Female();
可以看到,由於使用的向上轉型,那麼Main類裡只需要一個dosleep方法即可,程式會根據傳入的引數自動去呼叫對應的子類方法。
一般在大型專案開發中,往往是多個類實現了同一個介面或繼承同一個父類,如果說不使用向上轉型,那麼也就是子類每有一個父類方法在父類中都需要書寫一個方法
這樣會造成整個專案的冗雜,而採用轉型的概念後,父類或者介面中只需要有一個方法即可,這樣會使上層邏輯抽象,獨立清晰方便以後擴充套件。
結尾在分析一下部落格裡出現很多的噁心例子來鞏固一下
public class A {
public String show(D obj) {
return ("A and D");
}
public String show(A obj) {
return ("A and A");
}
}
public class B extends A{
public String show(B obj){
return ("B and B");
}
public String show(A obj){
return ("B and A");
}
}
public class C extends B{
}
public class D extends B{
}
public class Test {
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
這個噁心的例子應該不止一次看到,我也來摻一腳給一下我自己的解法
對於前三個而言均是a1的方法呼叫,變數a1是直接指向物件A的,即最常見的A a = new A()的情況,那麼可能的答案就只有A and A 和 A and D兩種
對於前兩個引數分別是b和c,在A的兩個方法裡沒有直接的指向,因此想到C繼承B繼承A,所以輸出結果既是A and A
對於第三個引數是d,在A中有明確的方法,因此輸出A and D (這個應該是最不會出錯的)
對於4~6而言均是a2的方法呼叫,變數a2是父類的引用指向子類物件B,參考前文在轉型的過程中會遺失方法show(B obj),又由於方法show(A obj)繼承自父類A
因此可能的答案只有B and A 和 A and D兩種
所以對於第四個和第五個的引數b和c,由於方法show(B obj)遺失了,因此只能找到最近的show(A obj),又由於這個方法被子類所重寫,因此輸出的是子類方法中的B and A而非父類的A and A
對於第六個引數是d ,在父類中有該方法,因此輸出A and D
對於7~9而言均是b的方法呼叫,變數b指向的是子類B的物件,而子類B繼承了父類A,因此可能的答案為B and B ,B and A ,A and D,父類的A and A被重寫了。
因此7~9的輸出也不再有什麼問題了。
參考部落格:http://blog.csdn.net/thinkGhoster/article/details/2307001
http://blog.csdn.net/mr_jj_lian/article/details/6860845