Skip to content

多态

概述

多态(Polymorphism)是面向对象三大特性之一,指同一类型的引用在不同运行时可以指向不同子类的对象,调用同一方法时实际执行的是当前对象所属类的实现。通俗说:父类引用指向子类对象,调用方法时看「对象」是谁,就执行谁的方法。多态让程序可以「面向抽象编程」,用统一的父类/接口类型处理多种具体子类型,提高可扩展性和可维护性。

多态建立在 继承方法重写之上:子类重写父类方法后,通过父类引用调用该方法时,JVM 会根据实际对象类型(运行时类型)决定执行哪个方法,称为运行时多态(或动态绑定)。

前置建议

已掌握 继承 中的 extendssuper方法重写@Override),理解「父类引用可指向子类对象」的写法。


为什么需要多态

  • 统一处理多种子类型:用父类类型声明的变量(如 Animal a)可以接收不同子类对象(DogCat),对 a 调用同一方法即可得到各自的行为,无需写大量 if-else 或 switch。
  • 可扩展:新增子类时,原有「面向父类」的代码不必修改,符合开闭原则。
  • 解耦:依赖抽象类型(父类/接口)而非具体实现,降低模块间耦合。

多态的三个必要条件

  1. 继承或实现:存在父类(或接口)与子类(或实现类)关系。
  2. 方法重写:子类重写了父类中的某方法。
  3. 父类引用指向子类对象:例如 Animal a = new Dog();,编译时类型是 Animal,运行时类型是 Dog

满足以上条件时,通过 a 调用被重写的方法,执行的是子类的实现。


基本语法与概念

编译时类型与运行时类型

java
Animal a = new Dog("Lucky");
  • 编译时类型(声明类型):Animal。编译器只根据 Animal 检查你能调用哪些方法(即 Animal 里声明的方法)。
  • 运行时类型(实际类型):Dog。运行时会根据实际对象Dog 来决定执行哪个类的方法实现。

因此:编译看左边(引用类型),运行看右边(对象类型)

多态下的方法调用

java
public class Animal {
    protected String name;
    public Animal(String name) { this.name = name; }
    public void speak() {
        System.out.println(name + " makes a sound.");
    }
}

public class Dog extends Animal {
    public Dog(String name) { super(name); }

    @Override
    public void speak() {
        System.out.println(name + " barks: Wang!");
    }
}

// 多态:父类引用指向子类对象
Animal a = new Dog("Lucky");
a.speak();   // 输出:Lucky barks: Wang!(执行的是 Dog 的 speak)

a 的编译时类型是 Animal,所以可以调用 speak();实际对象是 Dog,所以执行的是 Dog 重写的 speak()。这就是运行时多态

注意

只有实例方法才有多态;静态方法成员变量没有多态。用父类引用调用 static 方法时,执行的是父类的静态方法;访问字段时,取的是父类中声明的字段(若子类有同名字段则「隐藏」父类字段,但通过父类引用访问到的仍是父类字段)。


向上转型与向下转型

向上转型(Upcasting)

子类对象赋给父类引用,称为向上转型。这是自动、安全的,不需要强转。

java
Dog d = new Dog("Lucky");
Animal a = d;        // 向上转型,等价于 Animal a = new Dog("Lucky");
a.speak();           // 多态:执行 Dog#speak()

向上转型后,通过 a 只能调用 Animal声明过的成员;子类独有的方法(如 bark())无法通过 a 直接调用。

向下转型(Downcasting)

父类引用转成子类引用,称为向下转型。必须使用强制类型转换,且只有在引用实际指向的确实是该子类(或其子类)对象时才安全,否则运行时会抛出 ClassCastException

java
Animal a = new Dog("Lucky");
// a.bark();        // 编译错误:Animal 没有 bark 方法

Dog d = (Dog) a;    // 向下转型:已知 a 实际是 Dog,转为 Dog 引用
d.bark();           // 可以调用 Dog 特有方法

若实际对象不是 Dog,强转会报错:

java
Animal a = new Cat("Mimi");
Dog d = (Dog) a;    // 运行时 ClassCastException:Cat 不能转为 Dog

因此向下转型前应先用 instanceof 判断,再转换。


instanceof 与安全向下转型

instanceof 用于判断引用指向的对象是否是某个类(或该类子类)的实例,避免错误的向下转型。

基本用法

java
Animal a = new Dog("Lucky");

if (a instanceof Dog) {
    Dog d = (Dog) a;
    d.bark();
}

if (a instanceof Cat) {
    Cat c = (Cat) a;  // 不会进入这里,a 实际是 Dog
    c.meow();
}

常见写法:先判断再转型

java
public void handle(Animal a) {
    a.speak();   // 多态调用

    if (a instanceof Dog) {
        Dog d = (Dog) a;
        d.bark();
    } else if (a instanceof Cat) {
        Cat c = (Cat) a;
        c.meow();
    }
}

提示

JDK 16+ 支持 instanceof 模式匹配,可简写为:if (a instanceof Dog d) { d.bark(); },无需再单独强转。若需兼容 JDK 8,仍使用 instanceof 判断后显式强转。


使用示例

示例 1:多态统一处理多种子类

用父类数组或集合统一存放不同子类对象,循环中调用同一方法即可表现不同行为。

java
public class Animal {
    protected String name;
    public Animal(String name) { this.name = name; }
    public void speak() { System.out.println(name + " ???"); }
}

public class Dog extends Animal {
    public Dog(String name) { super(name); }
    @Override
    public void speak() { System.out.println(name + " barks: Wang!"); }
}

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

// 多态:用父类数组统一持有不同子类对象
Animal[] animals = {
    new Dog("Lucky"),
    new Cat("Mimi"),
    new Dog("Max")
};

for (Animal a : animals) {
    a.speak();   // 各自执行 Dog 或 Cat 的 speak
}
// 输出:
// Lucky barks: Wang!
// Mimi says: Miao!
// Max barks: Wang!

示例 2:方法参数多态(面向抽象编程)

方法参数使用父类类型,调用方可以传入任意子类对象,方法内部用多态统一处理。

java
public class Vet {
    /** 给任意动物「看病」,不关心具体是狗还是猫 */
    public void check(Animal a) {
        System.out.println("Checking: " + a.name);
        a.speak();   // 多态:实际对象是 Dog 就 bark,是 Cat 就 meow
    }
}

// 使用
Vet vet = new Vet();
vet.check(new Dog("Lucky"));   // Checking: Lucky \n Lucky barks: Wang!
vet.check(new Cat("Mimi"));    // Checking: Mimi \n Mimi says: Miao!

这样新增子类(如 Bird)时,Vet#check 不必修改,只需传入 new Bird("Tweet") 即可,符合对扩展开放、对修改关闭

示例 3:返回值多态与向下转型

方法返回父类类型,实际返回的可以是不同子类对象;调用方若需要子类能力,可先 instanceof 再向下转型。

java
public class AnimalFactory {
    public static Animal create(String type, String name) {
        if ("dog".equals(type)) return new Dog(name);
        if ("cat".equals(type)) return new Cat(name);
        return null;
    }
}

Animal a = AnimalFactory.create("dog", "Lucky");
a.speak();   // 多态:Lucky barks: Wang!

if (a instanceof Dog d) {
    d.bark();   // 使用子类特有方法(JDK 16+ 写法)
}

多态与属性、静态方法

  • 实例方法:有多态。通过父类引用调用时,执行实际对象所属类的方法。
  • 成员变量:没有多态。通过父类引用访问字段时,取的是父类中定义的该字段(子类同名字段会隐藏父类字段,但引用类型决定访问哪一份)。
  • 静态方法:没有多态。属于类而非对象,通过引用调用时也是看编译时类型,即父类的静态方法。
java
public class Parent {
    public String value = "parent";
    public static void staticMethod() { System.out.println("Parent.static"); }
    public void instanceMethod() { System.out.println("Parent.instance"); }
}

public class Child extends Parent {
    public String value = "child";
    public static void staticMethod() { System.out.println("Child.static"); }
    @Override
    public void instanceMethod() { System.out.println("Child.instance"); }
}

Parent p = new Child();
System.out.println(p.value);     // "parent"(看引用类型,无多态)
p.staticMethod();                // Parent.static(静态方法无多态)
p.instanceMethod();              // Child.instance(实例方法有多态)

提示

实际开发中应避免「用子类变量隐藏父类字段」;多态主要利用方法重写体现不同行为,属性一般通过 getter/setter 访问。


注意事项

  • 编译看左边,运行看右边:能调用哪些方法由编译时类型决定,实际执行哪个方法由运行时类型决定。
  • 向下转型前用 instanceof:避免 ClassCastException,或使用 JDK 16+ 的 instanceof 模式匹配。
  • 静态方法、成员变量无多态:多态只针对实例方法的重写。
  • 多态依赖方法重写:子类没有重写的方法,通过父类引用调用时执行的是父类实现;要体现多态,必须在子类中重写该方法。

注意

重写方法时需遵守 继承 中的重写规则:签名一致、返回类型协变、访问权限不能更严格等;建议使用 @Override 注解。


相关链接

基于 VitePress 构建