CodeGym /课程 /JAVA 25 SELF /多态的概念及其作用

多态的概念及其作用

JAVA 25 SELF
第 18 级 , 课程 0
可用

1. 引言

多态是面向对象编程的三大支柱之一(与继承和封装并列)。从字面看,该词源自希腊语 “poly”(多)与 “morph”(形)。在编程中,这意味着:一个接口,对应多种实现

定义

多态是指不同类的对象对同一消息(方法调用)以不同方式作出响应的能力。

也就是说,如果你有一个方法 makeSound(),你可以对任何动物调用它,但猫会喵叫,狗会汪叫,牛会哞叫。对程序员来说,这只是一次 animal.makeSound() 的调用;而实际发生什么——取决于该变量后面真实对象的类型。

生活类比

设想你家里有一个电视遥控器,用它还能控制音箱、投影仪,甚至咖啡机。你按下“开机”——turnOn(),每个设备都会用自己的方式响应。关键在于大家都有“开机”这个“按钮”,但实现各不相同。

Java 示例

class Animal {
    void makeSound() {
        System.out.println("某种声音...");
    }
}

class Dog extends Animal {
    @Override
    void makeSound() {
        System.out.println("汪!");
    }
}

class Cat extends Animal {
    @Override
    void makeSound() {
        System.out.println("喵!");
    }
}

class Cow extends Animal {
    @Override
    void makeSound() {
        System.out.println("哞!");
    }
}

现在我们可以这样做:

Animal animal1 = new Dog();
Animal animal2 = new Cat();
Animal animal3 = new Cow();

animal1.makeSound(); // 汪!
animal2.makeSound(); // 喵!
animal3.makeSound(); // 哞!

请注意:所有变量的类型都是 Animal,但调用结果取决于对象的实际类型。

2. 多态的类型

在 Java(以及大多数面向对象语言)中,通常区分两种主要的多态:

编译期(静态)多态 — 方法重载(overloading)

即在同一个类中存在多个同名但参数不同的方法。编译器会根据传入的实参来决定调用哪个方法。

示例(稍作预告——细节在下一讲中):

class Printer {
    void print(int x) {
        System.out.println("数字: " + x);
    }

    void print(String s) {
        System.out.println("字符串: " + s);
    }
}

运行期(动态)多态 — 方法重写(overriding)

即在基类中定义一个方法,然后在子类中对其进行重写。究竟调用哪一个方法实现——在程序运行时(runtime)根据对象的实际类型来决定。

示例可参见上面的动物案例。

3. 为什么需要多态?

多态不仅仅是面试中的“漂亮词”。它是让代码更灵活、可扩展、易维护的工具。

代码的通用性

你可以编写只面向基类型的代码,而不用关心具体实现细节。比如,如果你有一个动物列表,你可以遍历它并对每个元素调用 makeSound(),无需关心它是猫还是狗。

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

for (Animal animal : animals) {
    animal.makeSound(); // 每次都会调用“正确”的方法
}

易于扩展

如果明天老板说:“我们加只鹦鹉吧!”,你只需写一个新的类 Parrot extends Animal 并把它加进数组。其余代码无需改动。这就是对扩展开放,对修改关闭(SOLID 中的 OCP 原则)。

简化架构

你可以构建复杂系统,让各部分通过抽象(基类或接口)互相协作,而不用关心具体实现。这能节省时间、精力和咖啡。

4. 关键概念:引用类型与实际类型

变量的引用类型
当你写下 Animal animal = new Dog(); 时,变量 animal 的引用类型是 Animal。也就是说,编译器“认为”它是一个 Animal,只允许调用在 Animal 类中声明的方法。

对象的实际(真实)类型
但内存中实际存放的是 Dog 类型的对象。正是它决定了通过 makeSound() 调用时会执行哪个方法。

示例

Animal animal = new Dog();
animal.makeSound(); // 将调用 Dog.makeSound(),而不是 Animal.makeSound()

重要! 通过基类引用(Animal)无法调用只在 Dog 中存在、且未在基类中声明的方法。

延迟(动态)绑定
这就是运行期发生的“魔法”:当你通过基类引用调用方法时,JVM 会查看对象的实际类型并调用“正确”的实现。这就是多态在起作用

5. 实用示例:应用中的多态

让我们继续开发教学用的小应用。假设我们在写一个简单的动物园模拟。我们有基类 Animal 以及它的若干子类。我们希望所有动物都能“发出声音”,但又不想为每种动物都写一套独立代码。

步骤 1:基类与子类

class Animal {
    void makeSound() {
        System.out.println("某种声音...");
    }
}

class Dog extends Animal {
    @Override
    void makeSound() {
        System.out.println("汪!");
    }
}

class Cat extends Animal {
    @Override
    void makeSound() {
        System.out.println("喵!");
    }
}

步骤 2:动物数组

Animal[] zoo = { new Dog(), new Cat(), new Animal() };

步骤 3:遍历并调用方法

for (Animal animal : zoo) {
    animal.makeSound();
}

运行结果:

汪!
喵!
某种声音...

请注意:我们并不知道数组里具体是谁——程序会自行分辨并调用正确的方法。

6. 多态的示意图


.        Animal (makeSound)
           /        \
        Dog        Cat
     (makeSound) (makeSound)

Animal animal = new Dog();
animal.makeSound(); // --> Dog.makeSound()

Animal animal = new Cat();
animal.makeSound(); // --> Cat.makeSound()

7. 另一个例子:真实业务中的多态

比如你在写一个公司人事管理程序。你有一个基类 Employee,以及两个子类:ManagerDeveloper。所有员工都会工作——work(),但工作方式各不相同。

class Employee {
    void work() {
        System.out.println("员工在工作。");
    }
}

class Manager extends Employee {
    @Override
    void work() {
        System.out.println("经理在开会。");
    }
}

class Developer extends Employee {
    @Override
    void work() {
        System.out.println("开发者在写代码。");
    }
}

现在你可以这样做:

Employee[] staff = { new Manager(), new Developer(), new Employee() };

for (Employee emp : staff) {
    emp.work();
}

结果:

经理在开会。
开发者在写代码。
员工在工作。

8. 何时多态不起作用

多态只对基类中声明的方法起作用。如果子类里有自己特有的方法,通过基类引用是看不到的。

class Dog extends Animal {
    void fetchStick() {
        System.out.println("狗叼回木棍!");
    }
}

Animal animal = new Dog();
// animal.fetchStick(); // 编译错误!通过 Animal 看不到该方法

要调用特有方法,需要进行类型转换:

if (animal instanceof Dog) {
    ((Dog) animal).fetchStick();
}

但这是另一个话题——要点是:通过多态只能访问在基类中声明的方法。

9. 使用多态时的常见错误

错误 1: 期望通过基类引用能访问子类的所有方法。实际上只能访问基类中声明的方法。

错误 2: 重写方法时不使用 @Override 注解。没有它,方法签名一旦写错就不会发生真正的重写,多态也就失效(基类方法不会被覆盖)。

错误 3: 未进行类型转换就尝试调用子类的特有方法。编译器不允许,因为它不知道基类引用背后具体是什么对象。

错误 4:重载(overloading)重写(overriding)混淆。重载是在同一类中用相同方法名、不同参数列表定义多个方法;重写是在子类中改变基类方法的行为。

评论 (1)
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION
ncksllpo 级别 33,Cherkasy,Ukraine
3 二月 2026
🤑