> For the complete documentation index, see [llms.txt](https://yubin551.gitbook.io/java-note/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://yubin551.gitbook.io/java-note/object_oriented_programming/polymorphism.md).

# 多型

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

## 何謂多型？ What is Polymorphism?

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

舉個例子：

```java
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("游...游...");
    }
}
```

我們有Animal類別，以及Dog、Bird、Fish各自繼承Animal。 ![](/files/-MLRnlR9fxSM5rs1-3FL)

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

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

程式如下：

```java
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()都呼叫看看。

執行結果：

```java
move...move...
跑...跑...
飛...飛...
游...游...
```

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

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

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

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

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

如果沒有多型的話，

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

```java
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這個介面的方法就好。

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

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

```java
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
```

執行結果：

```java
move...move...
跑...跑...
飛...飛...
游...游...
```

## 隱性轉型 Implicit Casting

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

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

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

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

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

## 轉型失敗 Casting Fail

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

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

範例程式：(Dog 繼承 Animal)

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

執行結果：

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

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

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