多态是什么?
在编程中,多态(polymorphism)是指同一段代码,比如一次函数调用或一个方法名,能处理不同类型的值,并由每种类型提供各自的行为。调用 shape.area() 时,圆形和正方形执行的是不同的计算。
更新于 2026年9月24日
在 Python 中,len("hello") 返回 5,len([4, 8, 15]) 返回 3,len({"a": 1}) 返回 1。同一个函数名用在三种类型上,每种类型按自己的方式计数:字符串数字符,列表数元素,字典数键。这个词来自希腊语:poly 意为“多”,morph 意为“形态”。一次调用,多种形态的行为。
多态是如何工作的
代码通过名字调用方法,比如 shape.area()。执行这一行时,语言会查看实际的对象,在这个对象自己的类上找到 area。这种查找叫作动态分派,正是它让同一行代码能对不同对象做不同的事:
Circle 3.14
Square 9
Circle 12.57
Python 查找方法时,先搜索对象所属的类,再按固定的顺序搜索它的父类,这个顺序叫作方法解析顺序。如果 Square 没有自己的 area(),搜索就会找到 Shape.area(),并抛出 NotImplementedError,以此提示每个子类都必须提供自己的实现。
循环从不检查它拿到的是哪种图形。要添加三角形,你只需写一个带有自己 area() 的 Triangle 类,循环一行都不用改。没有多态的话,循环就得为每种图形写一个 if isinstance(...) 分支,每出现一种新图形都要改一次。
多态的类型
| 类型 | 别名 | 表现形式 | 何时选定具体版本 |
|---|---|---|---|
| 子类型多态 | 运行时多态、方法重写 | 子类替换父类的某个方法 | 程序运行时 |
| 特设多态 | 重载 | 同一个名字,针对不同参数类型有多个版本;也包括运算符重载 | 在 Java 和 C++ 中于编译时 |
| 参数多态 | 泛型、模板 | 一段代码适用于任何类型:Java 的 List<T>,C++ 的 vector<T> | 编译时 |
在面向对象编程中,人们不加说明地提到“多态”时,几乎总是指第一行:通过重写实现的子类型多态。
Python 以自己的方式支持这三种多态。重写的用法就像图形的例子那样。+ 这样的运算符属于特设多态。max() 和 sorted() 这样的函数在思路上属于参数多态:只要元素之间可以比较,它们就能接受由数字、字符串或日期组成的列表,而 list[int] 或 TypeVar 这样的类型提示可以把这一点明确写出来。
Java 中的编译时多态和运行时多态
Java 两种都有,面试题也常问它们的区别:
class Animal { String sound() { return "..."; } }
class Dog extends Animal {
@Override String sound() { return "Woof"; }
}
Animal pet = new Dog();
pet.sound(); // "Woof": chosen at run time from the object
int add(int a, int b) { return a + b; }
double add(double a, double b) { return a + b; }
add(2, 3); // int version, chosen by the compiler
add(2.5, 1.0); // double version
变量 pet 的类型是 Animal,执行的却是 Dog 的版本,因为选择取决于对象,而不是变量。两个 add 方法属于重载:编译器在程序运行之前,根据实参的类型选出其中一个。接口不共享代码,也能带来同样的运行时行为:任何实现了 Comparable 的类都可以用 Collections.sort 排序。C++ 的机制相同,只有一点不同:方法只有声明为 virtual,才会在运行时分派。参见 Java 多态、方法重载和 C++ 虚函数。
Python 中的多态:鸭子类型和运算符
Python 不要求有共同的父类。只要对象拥有代码所调用的方法,就能正常工作:“如果它走路像鸭子,叫声也像鸭子,那它就是鸭子”。运算符也是多态的。写 a + b 会调用 a.__add__(b),所以你自己的类也可以支持 +、len() 和 print():
3 abcd [1, 2]
Vector(11, 22)
5 3 2
继承是实现多态的一种方式,而不是必要条件。Vector 没有继承任何特别的类,len() 和 + 却能像对待内置类型一样对待它。Python 继承文档介绍了通过子类实现的方式。
常见错误
以为 Python 会按函数签名重载。 再写一个同名的 def,会悄无声息地替换掉前一个:
Hi Ana Silva
Traceback (most recent call last):
...
TypeError: greet() missing 1 required positional argument: 'last'
改用默认参数:def greet(first, last="")。
在 C++ 中忘了写 virtual。 没有它,通过基类指针调用方法时,执行的是基类的版本,即使对象其实是子类。
重写改变了方法的含义。 如果某个子类的 area() 返回的是周长,所有信任 area() 的调用方都会出错。重写必须遵守父类作出的约定;这条规则叫作里氏替换原则。
到处做类型检查。 一长串 if type(x) == ... 分支,通常说明应该让每种类型自带对应的方法。
下一步学什么
多态和封装、抽象配合使用:封装保护每个对象的数据,抽象定义各个类型共同实现的接口。可以在 Python 类文档中练习方法重写,或者试试 Java 课程,在那里重载和重写会同时出现。
常见问题
多态有哪些例子?
+ 运算符就是一个:1 + 2 把数字相加,"ab" + "cd" 拼接字符串,[1] + [2] 拼接列表。同一个符号作用于三种类型,由每种类型自己决定它的含义。在面向对象的代码里,经典的例子是一个图形列表,其中每个图形都用自己的公式来回答 area()。多态和继承有什么区别?
继承让一个类复用父类的代码。多态让代码通过一个共同的接口来处理不同的类型。继承是实现多态的一种常见方式,因为子类可以重写父类的方法,但 Python 的鸭子类型和 Java 的接口不共享任何代码,同样能实现多态。
生物学中的多态是什么?
在生物学中,多态性是指同一个种群中存在某种性状或基因的两种或更多形式,比如人类的 A、B、AB 和 O 血型。它和编程术语同样来自希腊语的“多种形态”,但这两个概念互不相关。
Python 支持方法重载吗?
def,会直接替换掉前一个。在 Python 中,可以用默认参数、*args 或 functools.singledispatch 达到同样的效果,后者会根据第一个参数的类型来选择函数。多态有什么用?
shape.draw(),添加三角形类时不需要改动这段代码,只要写好新类就行。没有多态的话,每多一种类型,那个循环就要多一个 if 分支。