Java 多型 (Polymorphism)

多型是指同一個方法在不同物件上有不同的行為,是物件導向的核心特性之一。

基本概念

父類別的參考可以指向子類別的物件:

Animal myDog = new Dog();   // 父類別參考指向子類別物件
Animal myCat = new Cat();

myDog.makeSound();  // Woof!(執行 Dog 的版本)
myCat.makeSound();  // Meow!(執行 Cat 的版本)

完整範例

// 父類別
public class Animal {
    protected String name;
    
    public Animal(String name) {
        this.name = name;
    }
    
    public void makeSound() {
        System.out.println("Some sound");
    }
}

// 子類別
public class Dog extends Animal {
    public Dog(String name) {
        super(name);
    }
    
    @Override
    public void makeSound() {
        System.out.println(name + " says: Woof!");
    }
}

public class Cat extends Animal {
    public Cat(String name) {
        super(name);
    }
    
    @Override
    public void makeSound() {
        System.out.println(name + " says: Meow!");
    }
}

// 使用多型
public class Main {
    public static void main(String[] args) {
        Animal[] animals = {
            new Dog("Buddy"),
            new Cat("Whiskers"),
            new Dog("Max")
        };
        
        // 多型:同一方法,不同行為
        for (Animal animal : animals) {
            animal.makeSound();
        }
    }
}
// 輸出:
// Buddy says: Woof!
// Whiskers says: Meow!
// Max says: Woof!

多型的好處

1. 統一處理不同型別

public void feedAnimals(Animal[] animals) {
    for (Animal animal : animals) {
        animal.eat();  // 不需要知道具體型別
    }
}

2. 程式碼擴展性

新增子類別不需要修改現有程式碼:

// 新增 Bird 類別
public class Bird extends Animal {
    @Override
    public void makeSound() {
        System.out.println(name + " says: Tweet!");
    }
}

// 不需要修改原有的迴圈程式碼

3. 減少重複程式碼

// 不用多型
public void processShape(Object shape) {
    if (shape instanceof Circle) {
        ((Circle) shape).draw();
    } else if (shape instanceof Rectangle) {
        ((Rectangle) shape).draw();
    }
    // 每新增一種圖形都要加 if
}

// 使用多型
public void processShape(Shape shape) {
    shape.draw();  // 自動呼叫正確的版本
}

向上轉型 (Upcasting)

子類別轉為父類別,自動進行:

Dog dog = new Dog("Buddy");
Animal animal = dog;  // 向上轉型(自動)

animal.makeSound();   // Woof!(仍執行 Dog 的版本)
// animal.bark();     // 錯誤!Animal 沒有 bark()

向下轉型 (Downcasting)

父類別轉為子類別,需要強制轉型:

Animal animal = new Dog("Buddy");

// 需要強制轉型
Dog dog = (Dog) animal;
dog.bark();  // OK

// 錯誤的轉型會拋出 ClassCastException
Animal cat = new Cat("Whiskers");
// Dog wrongDog = (Dog) cat;  // ClassCastException!

安全的向下轉型

Animal animal = getAnimal();

if (animal instanceof Dog) {
    Dog dog = (Dog) animal;
    dog.bark();
}

// Java 16+ 模式匹配
if (animal instanceof Dog dog) {
    dog.bark();  // 自動轉型
}

方法覆寫規則

子類別覆寫方法時:

  • 方法簽章必須相同
  • 存取權限不能更嚴格
  • 回傳型別可以是子型別(協變回傳)
public class Animal {
    protected Animal getParent() {
        return null;
    }
}

public class Dog extends Animal {
    @Override
    protected Dog getParent() {  // 協變回傳
        return null;
    }
}

更多說明請參考 Java 方法覆寫

實際應用

支付系統

public abstract class Payment {
    public abstract void pay(double amount);
}

public class CreditCard extends Payment {
    @Override
    public void pay(double amount) {
        System.out.println("信用卡支付 $" + amount);
    }
}

public class PayPal extends Payment {
    @Override
    public void pay(double amount) {
        System.out.println("PayPal 支付 $" + amount);
    }
}

// 使用
public void checkout(Payment payment, double amount) {
    payment.pay(amount);  // 多型
}

checkout(new CreditCard(), 100);  // 信用卡支付 $100
checkout(new PayPal(), 100);      // PayPal 支付 $100

圖形繪製

public abstract class Shape {
    public abstract double area();
    public abstract void draw();
}

public class Circle extends Shape {
    private double radius;
    
    @Override
    public double area() {
        return Math.PI * radius * radius;
    }
    
    @Override
    public void draw() {
        System.out.println("Drawing circle");
    }
}

// 統一處理所有圖形
public void processShapes(Shape[] shapes) {
    for (Shape shape : shapes) {
        shape.draw();
        System.out.println("Area: " + shape.area());
    }
}

多型的類型

類型說明
執行時期多型方法覆寫(本篇主題)
編譯時期多型方法多載