向上轉型和向下轉型


多態的條件

繼承。

在多態中必須存在有繼承關系的子類和父類。


重寫。

子類對父類中某些方法進行重新定義,在調用這些方法時就會調用子類的方法。


向上轉型。

在多態中需要將子類的引用賦給父類對象,只有這樣該引用才能夠具備技能調用父類的方法和子類的方法
繼承也可以替換為實現接口。


向上轉型

子類引用的對象轉換為父類類型稱為向上轉型。通俗地說就是是將子類對象轉為父類對象。此處父類對象可以是接口。

 

案例驅動

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。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM