Java-多態(向上轉型向下轉型)

多態的引入

多態就是指程序中定義的引用變量所指向的具體類型和通過該引用變量發出的方法調用在編程時並不確定,而是在程序運行期間才確定,即一個引用變量倒底會指向哪個類的實例對象,該引用變量發出的方法調用到底是哪個類中實現的方法,必須在由程序運行期間才能決定。

比如你回家發現桌上有幾個杯子裏面都裝了白酒,從外面看我們不知道這是些什麼酒,只有喝了之後才能猜出來是什麼酒。你一喝,是劍南春、再喝是五糧液、再喝是酒鬼酒….我們可以描述成如下:

  酒 wine1 = 劍南春
  酒 wine2 = 五糧液
  酒 wine3 = 酒鬼酒

上面表現的的就是多態。劍南春、五糧液、酒鬼酒都是酒的子類,我們通過酒這一父類就能夠引用不同的子類,這就是多態。

要理解多態我們就必須要明白什麼是“向上轉型”。在上面喝酒的例子中,酒(Wine)是父類,劍南春(JNC)、五糧液(WLY)、酒鬼酒(JGJ)是子類。我們定義如下代碼:

  JNC wine1 = new  JNC();

對於這個代碼我們非常容易理解就是實例化了一個劍南春的對象,但是這樣呢?

  Wine wine1 = new JNC();

在這裏我們這樣理解,這裏定義了一個Wine 類型的wine1,它指向JNC對象實例。由於JNC是繼承與Wine,所以JNC可以自動向上轉型爲Wine,所以wine1是可以指向JNC實例對象的。

但是向上轉型存在一些缺憾,那就是它必定會導致一些方法和屬性的丟失,而導致我們不能夠獲取它們。所以父類類型的引用可以調用父類中定義的所有屬性和方法,對於只存在子類中的方法和屬性它就不能使用(只能使用父類有的方法(也就是子類重寫父類的方法,子類擴展的、特有的屬性方法不能被向上轉型的父類引用使用))。

public class Wine {
        public void fun1(){
            System.out.println("Wine 的 Fun1...");
            fun2();
        }
        public void fun2(){
            System.out.println("Wine 的 Fun2...");
        }
    }

    public class JNC extends Wine{
        /**
         * 子類 重載 父類方法 
         * 
         * 屬於 JNC子類 特有方法 向上轉型後的wine不能調用
         *
         * 父類中不存在該方法,向上轉型後,父類是不能引用該方法的
         */
        public void fun1(String string){
            System.out.println("JNC 的 Fun1...");
            fun2();
        }
        /**
         * 子類 重寫 父類方法
         *
         * 指向子類的父類引用調用fun2時,必定是調用該方法
         */
        public void fun2(){
            System.out.println("JNC 的 Fun2...");
        }
    }

    public class Test {
        public static void main(String[] args) {
            Wine wineJNC = new JNC();
            wineJNC.fun1();
        }
    }
-------------------------------------------------
輸出結果:
Wine 的 Fun1...
JNC 的 Fun2...

從程序的運行結果中發現,wineJNC.fun1()首先是運行父類Wine中的fun1().然後再運行子類JNC中的fun2()。

分析:在這個程序中子類 JNC 重載 了父類 Wine 的方法fun1(),重寫 fun2(),而且 重載 後的fun1(String string)與 fun1()不是同一個方法,由於父類中沒有該方法,向上轉型後會丟失該方法,所以執行JNC的Wine類型引用wineJNC是不能引用fun1(String string)方法。而子類JNC重寫了fun2() ,那麼指向JNC的wineJNC引用會調用JNC中fun2()方法。

多態的實現

Java實現多態有三個必要條件:繼承、重寫、向上轉型。

  • 繼承:在多態中必須存在有繼承關係的子類和父類。
  • 重寫:子類對父類中某些方法進行重新定義,在調用這些方法時就會調用子類的方法。
  • 向上轉型:在多態中需要將子類的引用賦給父類對象,只有這樣該引用才能夠具備技能調用父類的方法和子類的方法。

對於Java而言,它多態的實現機制遵循一個原則:當超類對象引用變量引用子類對象時,被引用對象的類型而不是引用變量的類型決定了調用誰的成員方法,但是這個被調用的方法必須是在超類中定義過的,也就是說被子類覆蓋的方法。

基於繼承的多態

基於繼承的實現機制主要表現在父類和繼承該父類的一個或多個子類對某些方法的重寫,多個子類對同一方法的重寫可以表現出不同的行爲。

public class Wine {

    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Wine(){
    }
    public String drink(){
        return "喝的是 " + getName();
    }
    /**
     * 重寫toString()
     */
    public String toString(){
        return null;
    }
}

class JNC extends Wine{

    public JNC(){
        setName("JNC");
    }
    /**
     * 重寫父類方法,實現多態
     */
    public String drink(){
        return "喝的是 " + getName();
    }
    /**
     * 重寫toString()
     */
    public String toString(){
        return "Wine : " + getName();
    }
}

class JGJ extends Wine{

    public JGJ(){
        setName("JGJ");
    }
    /**
     * 重寫父類方法,實現多態
     */
    public String drink(){
        return "喝的是 " + getName();
    }
    /**
     * 重寫toString()
     */
    public String toString(){
        return "Wine : " + getName();
    }

    public static void main(String[] args) {
        //定義父類數組
        Wine[] wines = new Wine[2];
        //定義兩個子類
        JNC jnc = new JNC();
        JGJ jgj = new JGJ();
        //父類引用子類對象,向上轉型
        wines[0] = jnc;
        wines[1] = jgj;
        for(int i = 0 ; i < 2 ; i++){
            System.out.println(wines[i].toString() + "--" 
            + wines[i].drink());
        }
    }
}
-------------------------------------
輸出結果:
Wine : JNC--喝的是 JNC
Wine : JGJ--喝的是 JGJ

在上面的代碼中JNC、JGJ繼承Wine,並且重寫了drink()、toString()方法,程序運行結果是調用子類中方法,輸出JNC、JGJ的名稱,這就是多態的表現。不同的對象可以執行相同的行爲,但是他們都需要通過自己的實現方式來執行,這是因爲向上轉型。

我們都知道所有的類都繼承自超類Object,toString()方法也是Object中方法,當我們這樣寫時:

 Object object = new JGJ();
 System.out.println(object.toString());

 輸出的結果是Wine : JGJ。

Object、Wine、JGJ三者繼承鏈關係是:JGJ—>Wine—>Object。所以我們可以這樣說:當子類重寫父類的方法被調用時,只有對象繼承鏈中的最末端的方法纔會被調用。但是注意如果這樣寫:

Object object = new Wine();
System.out.println(object.toString());

輸出的結果應該是Null,因爲JGJ並不存在於該對象繼承鏈中。

基於繼承實現的多態總結如下:對於引用子類的父類類型,在處理該引用時,它適用於繼承該父類的所有子類,子類對象的不同,對方法的實現也就不同,執行相同動作產生的行爲也就不同。

如果父類是抽象類,那麼子類必須要實現父類中所有的抽象方法,這樣該父類所有的子類一定存在統一的對外接口,但其內部的具體實現可以各異,這樣我們就可以使用頂層類提供的統一接口來處理該層次的方法。

基於接口實現的多態

在接口的多態中,指向接口的引用必須是指定這實現了該接口的一個類的實例程序,在運行時,根據對象引用的實際類型來執行對應的方法。

繼承都是單繼承,只能爲一組相關的類提供一致的服務接口。但是接口可以是多繼承多實現,它能夠利用一組相關或者不相關的接口進行組合與擴充,能夠對外提供一致的服務接口。所以它相對於繼承來說有更好的靈活性。

向上轉型

父類對象指向(引用)子類對象稱爲向上轉型。通俗地說就是是將子類對象轉爲父類對象。此處父類對象可以是接口。

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 dog 
Animal animal1 = new Cat();
Animal animal2 = new Animal();
Cat cat1 = ((Cat) animal1);
cat1.eat(); //輸出 我喫魚 
Dog dog = ((Dog) animal1);
dog.eat(); // 報錯 : java.lang.ClassCastException:com.chengfan.animal.Cat cannot be cast to com.chengfan.animal.Dog Animal a1 = new Animal();
Cat cat2 = ((Cat) animal2);
c1.eat(); // 報錯 : java.lang.ClassCastException:com.chengfan.animal.Animal cannot be cast to com.chengfan.animal.Cat

animal1本身就是Cat對象,所以它可以向下轉型爲Cat,也當然不能轉爲Dog,你見過一隻貓能就變成一條狗嗎?

animal2本身就是Animal對象,它也不能被向下轉型爲任何子類對象。比如貓是動物,但是動物一定是貓嗎?也可以是狗,所以animal2不能轉換成其他類,也就是不能向下轉型。

注意:

  • 向下轉型的前提是父類對象指向的是子類對象(也就是說,在向下轉型之前,它得先向上轉型)
  • 向下轉型只能轉型爲本類對象(也就是說,向下轉型的類只能是進行向上轉型中的子類,貓是不能變成狗的)
  • 進行向下轉型後的引用就可以調用子類的特殊方法和屬性了

參考:https://www.cnblogs.com/chenssy/p/3372798.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章