多型

介紹多型的觀念及程式基本撰寫方式。

何謂多型? What is Polymorphism?

多型 Polymorphism 一詞是指生物學中一個生物或物種可以有不同的形式或階段。 在物件導向程式設計的概念中,利用父類別提供的方法呼叫,子類別可以有自己特有的行為。

舉個例子:

class Animal {
    void move() {
        System.out.println("move...move...");
    }
}
class Dog extends Animal {
    void move() {
        System.out.println("跑...跑...");
    }
}
class Bird extends Animal {
    void move() {
        System.out.println("飛...飛...");
    }
}
class Fish extends Animal {
    void move() {
        System.out.println("游...游...");
    }
}

還記得繼承關係可以用『is a』來表述嗎? 我可以說 Dog is a Animal. 或 Bird is a Animal. 或 Fish is a Animal. 這是OK且符合邏輯的。

好,那現在我有4隻動物,是什麼動物不管,我就把牠們都當成動物就好。 我知道動物裡面有一個move()的方法,我讓每隻動物都使用這個move()的方法。

程式如下:

class Test {
    public static void main(String[] args) {

        Animal a =new Animal();
        Animal b =new Dog();
        Animal c =new Bird();
        Animal d =new Fish();

        a.move();
        b.move();
        c.move();
        d.move();

    }// end of main(String[])
}// end of class Test

變數a看起來OK,宣告是Animal且也用Animal的建構子去初始化。

變數b看起來就怪怪的,用Dog()來初始化的話不是應該宣告成Dog型態嗎? 是的,那樣寫也OK,我們知道依據繼承關係,『Dog is a Animal.』所以我這樣寫也並非違反邏輯。 利用Dog()建構子創造的物件,理當擁有Dog類別定義的所有成員,但不管,現在我們只把這個物件當作Animal來看。

變數c,不管你怎麼建構你的物件,我都會用Animal的角度去看你。

變數d:本體是Fish,但有人把我當成動物。 (變數b,c,d是差不多情況)

好,現在我不管牠們4個的『本質』是什麼,我只知道我把牠們都當做 Animal 來看,而我知道 Animal 裡面有定義一個方法叫做 move(),所以把4隻Animal的move()都呼叫看看。

執行結果:

move...move...
......
......
......

可以看到,雖然都宣告為 Animal,也都呼叫了Animal裡面的move()方法,但每個物件表現出來的都不一樣,因為他們的『本質』不一樣。

這就是多型 Polymorphism 的簡單範例。

從上面的例子可以看出,我們只要管他是不是動物就好,不用管他的本質是什麼 ,就可以對他進行處理。

 void moveAnimal(Animal ani){
     ani.move();
 }

只要可以視為Animal的物件,就可以使用這個方法而不會出錯。 這樣的設計方式可以降低方法定義對類別的依賴,使用一個制定好的介面,利用該介面來操作不同的物件,增加程式的彈性及可維護性,設計上也比較有架構。

如果沒有多型的話,

要設計一個方法用來呼叫 Animal、Dog、Bird、Fish的移動move()方法,會變成這樣:

void  moveAnimal(Animal ani){
    ani.move();
}
void  moveDog(Dog dog){
    dog.move();
}
void  moveBird(Bird bird){
    bird.move();
}
void  moveFish(Fish fish){
    fish.move();
}

在有多型的情況下,我知道Dog、Bird、Fish都可以被當成Animal,而我們要呼叫的move()也剛好有被定義在Animal中,所以只需要制定一個可以操作Animal這個介面的方法就好。

void moveAnimal(Animal ani){
  ani.move();
}

使用這個方法,就可以對所有『可以被當成Animal的物件』進行操作:

class Test {
    public static void main(String[] args) {

        Animal a=new Animal();
        Dog d =new Dog();
        Bird b = new Bird();
        Fish f =new Fish();

        moveAnimal(a);
        moveAnimal(d);
        moveAnimal(b);
        moveAnimal(f);

    }    
    static void  moveAnimal(Animal ani){
        ani.move();
    }
}// end of class Test

執行結果:

move...move...
......
......
......

隱性轉型 Implicit Casting

在上面程式中,我們定義了moveAnimal(Animal ani)的方法,他會接受一個Animal參數進來,但程式中我們卻給他Dog、Bird、Fish這些類別的物件,為什麼能動?

因為Java會幫你進行型態轉換,子類別必定擁有母類別的所有屬性、所有方法,所以子類別一定可以轉型為母類別,而不會出錯。

Dog d = new Dog();
Animal a1 = d;  // Java幫你作了型態轉換,但你看不到,等價於下行
Animal a2 = (Animal)d;  // 自己寫是一樣的

moveAnimal(d); // 等價於下行
moveAnimal((Animal)d);

因為這個機制,所以搭配多型的設計方法,程式撰寫上變得非常便利。

轉型失敗 Casting Fail

我們知道利用小括號裡面放型態可以進行強制型態轉換,但會不會有問題?

以繼承來說,子類別是母類別的『延伸』,所以子類別轉型成母類別是絕對OK的,因為擁有母類別的所有欄位、所有方法。 但若母類別轉型成子類別呢?

範例程式:(Dog 繼承 Animal)

Animal ani = new Animal();
Dog d = (Dog)ani;  // 母類別強制轉型為子類別

執行結果:

Exception in thread "main" java.lang.ClassCastException:
Animal cannot be cast to Dog

沒錯,執行的時候發生了錯誤,產生ClassCastExeption例外。

強制轉型要付出的代價是工程師自己要負責的,以上述程式來說,『編譯會過,執行會錯』,因為母類別不一定擁有子類別的欄位,所以不能這樣轉換。

Last updated