Skip to content

第8课:多态

🎯 学习目标

  • 理解多态的概念
  • 掌握向上转型和向下转型
  • 理解动态绑定
  • 掌握 instanceof 运算符

📖 一、多态的概念

什么是多态?

多态(Polymorphism):同一个引用类型,指向不同的对象时,有不同的行为。

java
Animal animal1 = new Dog();
animal1.makeSound();  // Woof!

Animal animal2 = new Cat();
animal2.makeSound();  // Meow!

// 同样是 Animal 类型,但行为不同

多态的三个必要条件

  1. ✅ 继承(或实现接口)
  2. ✅ 方法重写
  3. ✅ 父类引用指向子类对象

📖 二、向上转型

1. 基本概念

向上转型(Upcasting):子类对象赋值给父类引用,自动完成。

java
class Animal {
    public void eat() {
        System.out.println("Animal eating");
    }
}

class Dog extends Animal {
    public void eat() {
        System.out.println("Dog eating");
    }
    
    public void bark() {
        System.out.println("Woof!");
    }
}

// 向上转型(自动)
Animal animal = new Dog();  // Dog → Animal

animal.eat();   // Dog eating(调用 Dog 的方法)
// animal.bark();  // ❌ 编译错误(Animal 没有 bark 方法)

2. 向上转型的特点

java
Animal animal = new Dog();

// ✅ 可以调用父类方法(重写后是子类实现)
animal.eat();

// ❌ 不能调用子类特有的方法
// animal.bark();  // 编译错误

// 可以访问父类的属性
System.out.println(animal.getClass());  // class Dog

📖 三、向下转型

1. 基本概念

向下转型(Downcasting):父类引用转为子类引用,需要强制转换。

java
Animal animal = new Dog();  // 向上转型

// 向下转型(强制)
Dog dog = (Dog) animal;
dog.bark();  // Woof!(现在可以调用 Dog 的方法了)

2. 向下转型的风险

java
Animal animal = new Cat();

// ❌ 运行时错误:ClassCastException
Dog dog = (Dog) animal;  // Cat 不能转为 Dog

3. instanceof 运算符

java
Animal animal = new Dog();

// 安全的向下转型
if (animal instanceof Dog) {
    Dog dog = (Dog) animal;
    dog.bark();
} else if (animal instanceof Cat) {
    Cat cat = (Cat) animal;
    cat.meow();
}

// Java 14+ 模式匹配
if (animal instanceof Dog dog) {
    dog.bark();  // 自动转型
}

📖 四、动态绑定

1. 静态绑定 vs 动态绑定

静态绑定:编译时确定(private、static、final 方法)
动态绑定:运行时确定(普通实例方法)

java
class Animal {
    public void method() {
        System.out.println("Animal");
    }
}

class Dog extends Animal {
    @Override
    public void method() {
        System.out.println("Dog");
    }
}

// 动态绑定
Animal animal = new Dog();
animal.method();  // Dog(运行时决定)

2. 属性不是多态的

java
class Animal {
    String name = "Animal";
}

class Dog extends Animal {
    String name = "Dog";
}

Animal animal = new Dog();
System.out.println(animal.name);  // Animal(属性看引用类型)

Dog dog = (Dog) animal;
System.out.println(dog.name);  // Dog

📖 五、多态的应用

1. 参数多态

java
public class AnimalTest {
    // 一个方法处理所有动物
    public static void feedAnimal(Animal animal) {
        animal.eat();
    }
    
    public static void main(String[] args) {
        feedAnimal(new Dog());  // Dog eating
        feedAnimal(new Cat());  // Cat eating
        feedAnimal(new Bird()); // Bird eating
    }
}

2. 数组多态

java
Animal[] animals = {
    new Dog(),
    new Cat(),
    new Bird()
};

for (Animal animal : animals) {
    animal.eat();
    
    // 调用子类特有方法
    if (animal instanceof Dog) {
        ((Dog) animal).bark();
    } else if (animal instanceof Cat) {
        ((Cat) animal).meow();
    }
}

3. 返回值多态

java
public class AnimalFactory {
    public static Animal createAnimal(String type) {
        switch (type) {
            case "dog":
                return new Dog();
            case "cat":
                return new Cat();
            default:
                return new Animal();
        }
    }
}

// 使用
Animal animal = AnimalFactory.createAnimal("dog");
animal.eat();

📖 六、多态的优点

1. 提高代码的扩展性

java
// 不用多态
public void process(Dog dog) { dog.eat(); }
public void process(Cat cat) { cat.eat(); }
public void process(Bird bird) { bird.eat(); }

// 使用多态
public void process(Animal animal) {
    animal.eat();  // 所有动物通用
}

2. 简化代码

java
// 不用多态
List<Dog> dogs = new ArrayList<>();
List<Cat> cats = new ArrayList<>();
// ...

// 使用多态
List<Animal> animals = new ArrayList<>();
animals.add(new Dog());
animals.add(new Cat());

3. 降低耦合度

java
// 面向接口编程
public void saveData(Database db) {
    db.save();
}

// 可以传入任何 Database 的实现
saveData(new MySQL());
saveData(new Oracle());
saveData(new MongoDB());

💡 最佳实践

1. 面向接口编程

java
// ✅ 好:使用接口
List<String> list = new ArrayList<>();

// ❌ 不好:使用具体类
ArrayList<String> list = new ArrayList<>();

2. 避免过度使用向下转型

java
// ❌ 不好:频繁向下转型
Animal animal = getAnimal();
if (animal instanceof Dog) {
    ((Dog) animal).bark();
} else if (animal instanceof Cat) {
    ((Cat) animal).meow();
}

// ✅ 好:在父类中定义统一接口
abstract class Animal {
    public abstract void makeSound();
}

3. 利用多态消除 if-else

java
// ❌ 不好
if (type.equals("dog")) {
    dog.eat();
} else if (type.equals("cat")) {
    cat.eat();
}

// ✅ 好
Animal animal = AnimalFactory.create(type);
animal.eat();

⚠️ 常见陷阱

陷阱1:以为字段也会多态

Java 的字段访问看引用类型,方法调用才有动态绑定。不要用同名字段表达多态行为。

陷阱2:频繁向下转型

如果代码里到处都是 instanceof 和强制转换,通常说明父类或接口抽象不完整。

陷阱3:重载和重写混淆

重载在编译期根据参数类型选择,重写在运行期根据对象实际类型选择。

陷阱4:父类构造器中调用可重写方法

构造过程中子类字段可能还没初始化,父类构造器调用被子类重写的方法容易出现半初始化问题。


🆚 Java vs C 对比

特性CJava 多态
动态行为函数指针表手写虚方法动态绑定
类型检查手动约定编译期类型系统
向下转型指针强转,风险大强转 + instanceof 检查
接口编程struct + 函数指针interface / abstract class

对 C 程序员来说,多态类似“结构体里放函数指针实现不同策略”,但 Java 把这套机制内建到类、接口和虚方法调用中。


🧪 实战案例:支付方式

不用多态的写法:

java
if (type.equals("wechat")) {
    payByWechat(amount);
} else if (type.equals("card")) {
    payByCard(amount);
}

使用多态:

java
interface Payment {
    void pay(BigDecimal amount);
}

class WechatPayment implements Payment {
    public void pay(BigDecimal amount) {
        System.out.println("wechat pay " + amount);
    }
}

class CardPayment implements Payment {
    public void pay(BigDecimal amount) {
        System.out.println("card pay " + amount);
    }
}

调用方只依赖接口:

java
public void checkout(Payment payment, BigDecimal amount) {
    payment.pay(amount);
}

新增支付方式时,新增实现类即可,调用方不需要知道具体类型。


✅ 掌握标准

学完本课后,应能做到:

text
能说出多态的三个必要条件。
能解释向上转型和向下转型。
能安全使用 instanceof。
能说明方法动态绑定和字段静态绑定的差异。
能区分重载和重写。
能用接口或父类参数接收不同实现。
能用多态减少 if-else 分支。
能识别过度向下转型带来的设计问题。

多态的价值是让调用方依赖抽象,而不是依赖一堆具体类型判断。


📝 练习

完成 练习/Ex08_Polymorphism.java

  1. 向上转型和向下转型
  2. instanceof 使用
  3. 动态绑定测试
  4. 参数多态
  5. 数组多态
  6. 综合:图形绘制系统

🎓 下一步

  • 第9课:抽象类与接口 - abstract、interface、区别与选择