在面向对象编程中,多态是三大核心特性(封装、继承、多态)之一,也是实现代码复用、提高程序扩展性的关键。掌握多态,能让你的代码更具优雅性和可维护性,尤其在大型项目开发中,多态的价值会体现得淋漓尽致。
简单来说,多态就是:同一件事情发生在不同的对象上,会产生不同的结果。
举个生活中的通俗例子:同样是「“打招呼”」这个行为,中国人可能会说“你好”,美国人可能会说“Hello”,法国人可能会说“Bonjour”——行为相同,执行主体(对象)不同,结果也就不同,这就是生活中的“多态”。
在 Java 中,这个概念同样适用:比如一个「“吃饭”」方法,调用在“猫”对象上就是“吃猫粮”,调用在“狗”对象上就是“吃狗粮”,调用在“人”对象上就是“吃米饭”,这就是 Java 中的多态。
一、多态的实现条件
多态并非凭空存在,它的实现需要满足三个核心条件,三者缺一不可,就像盖房子需要地基、墙体、屋顶一样,缺少任何一个都无法建成完整的房屋。
- 必须处于继承体系之下(包括类的直接/间接继承,或接口的实现)
- 子类(或实现类)必须对父类(或接口)的方法进行重写(Override)
- 必须通过父类(或接口)的引用来调用被重写的方法
这里需要额外说明:接口的实现本质上可以看作一种“特殊的继承”,因此实现接口并覆写接口中的抽象方法,也满足多态的实现条件,这也是实际开发中多态的常用场景之一。
二、重写(Override)
要实现多态,重写是核心环节。如果说继承是多态的“前提”,那么重写就是多态的“灵魂”,没有重写,就无法体现“同一行为,不同结果”的多态特性。
代码示例
// 父类:动物
public class Animal {
// 动物名称
public String name;
// 动物年龄
public int age;
// 构造方法:初始化动物名称
public Animal(String name) {
this.name = name;
}
// 动物的吃饭行为(父类默认实现)
public void eat() {
System.out.println(this.name + "正在进食");
}
// 动物的睡觉行为(父类默认实现)
public void sleep() {
System.out.println(this.name + "正在睡觉");
}
}
// 子类:猫(继承自动物类)
public class Cat extends Animal{
// 子类构造方法,调用父类构造方法初始化名称
public Cat(String name) {
super(name);
}
// 重写父类的eat方法,体现猫的特有进食行为
@Override
public void eat() {
System.out.println(this.name + "正在吃猫粮,细嚼慢咽中~");
}
}
重写方法的核心规则
重写不是随意修改父类方法,而是需要遵循严格的“约定”,这些约定是保证多态能够正常生效的关键,总结为以下 4 点:
- 子类重写父类方法时,方法名、参数列表(类型、个数、参数顺序) 必须与父类完全一致(如果参数列表不同,就不是重写而是重载了)
- 子类重写方法的返回值类型,要么与父类方法返回值类型完全一致,要么是父类返回值类型的子类(即协变返回类型)
- 示例:父类方法返回
Animal,子类重写后可以返回Cat(Cat是Animal的子类)
- 示例:父类方法返回
- 子类重写方法的访问权限,不能比父类对应的方法更严格(可以更宽松或保持一致)
- 示例:父类方法是
public,子类重写后不能是protected或private;父类方法是protected,子类重写后可以是public或protected
- 示例:父类方法是
- 特殊修饰符限制:父类被
private、static、final修饰的方法无法被重写private方法:父类私有方法对子类不可见,子类无法访问也就无法重写static方法:静态方法属于类本身,不属于对象,而多态是基于对象的,因此无法重写(子类可以定义同名静态方法,但这是“隐藏”而非“重写”)final方法:final关键字的作用是“禁止修改”,被final修饰的方法无法被子类重写,常用于保护核心方法不被篡改
三、重写与重载的核心对比
在 Java 中,重写(Override)和重载(Overload)是两个容易混淆的概念,二者都属于方法的特殊特性,但本质和用途截然不同。下面通过表格清晰对比二者的核心区别:
| 区别维度 | 重写(Override) | 重载(Overload) |
|---|---|---|
| 访问限定符 | 不能比父类方法更严格(可宽松/一致) | 可以自由修改,无强制限制 |
| 返回值类型 | 基本不变(支持协变返回类型) | 可以自由修改,无强制限制 |
| 参数列表 | 不能修改(方法名+参数列表需完全一致) | 必须修改(个数/类型/参数顺序至少有一项不同) |
| 绑定类型 | 动态绑定(运行时确定调用哪个方法) | 静态绑定(编译时就确定调用哪个方法) |
| 存在范围 | 仅存在于父子类(或接口与实现类)的继承体系中 | 仅存在于同一个类中(包括子类继承的父类方法与子类自身方法的重载) |
| 核心用途 | 实现多态,体现对象的特有行为 | 方便用户调用同一功能的方法,简化方法命名 |
补充说明:
- 动态绑定:程序运行时,根据对象的实际类型来确定要调用的方法,这是多态的核心底层支撑
- 静态绑定:程序编译时,就已经确定要调用的方法,与对象实际类型无关
四、向上转型
在满足继承和重写的条件后,要实现多态还需要最后一步——向上转型,它是多态的“载体”,也是实现多态的必要操作,且向上转型本身是安全无风险的。
概念说明
向上转型,就是创建一个子类对象,将其赋值给父类类型的引用变量,本质是父类引用指向子类对象。
由于子类是对父类的扩展,子类包含了父类的所有属性和方法(非私有),因此将子类对象赋值给父类引用,就像“把一个小杯子里的水倒进一个大杯子里”,不会出现溢出,所以是安全的。
向上转型后,父类引用只能访问父类中定义的属性和方法(包括被子类重写的方法),无法访问子类独有的属性和方法。
语法格式
// 语法:父类类型 引用变量名 = new 子类类型();
父类类型 对象名 = new 子类类型();
代码示例
// 父类:Animal(延续上文的定义)
// 子类:Dog(新增一个狗类,继承自Animal并改写eat方法)
public class Dog extends Animal {
public Dog(String name) {
super(name);
}
// 重写父类的eat方法,体现狗的特有进食行为
@Override
public void eat() {
System.out.println(this.name + "正在啃骨头,嘎嘣脆~");
}
}
// 测试类:演示向上转型
public class PolymorphismTest {
public static void main(String[] args) {
// 向上转型:父类Animal引用指向子类Cat对象
Animal cat = new Cat("小花猫");
// 调用的是Cat子类重写后的eat方法(体现多态)
cat.eat();
// 向上转型:父类Animal引用指向子类Dog对象
Animal dog = new Dog("大黄狗");
// 调用的是Dog子类重写后的eat方法(体现多态)
dog.eat();
}
}
运行结果
小花猫正在吃猫粮,细嚼慢咽中~
大黄狗正在啃骨头,嘎嘣脆~
从运行结果可以看出,同样是 Animal 类型的引用,调用 eat() 方法时,根据指向的子类对象不同,产生了不同的结果,这就是多态的直观体现。
五、向下转型
向上转型虽然安全且能实现多态,但也有一个局限:父类引用无法访问子类独有的属性和方法。如果我们需要使用子类的特有功能,就需要将父类引用还原为子类类型,这就是向下转型。
概念说明
向下转型,是将已经完成向上转型的父类引用,转换回子类类型的操作,本质是将“父类引用”还原为“子类对象”,从而能够访问子类独有的属性和方法。
注意:向下转型存在类型不匹配的风险,就像“把大杯子里的水倒进小杯子里,可能会溢出”一样,如果父类引用指向的不是当前子类类型的对象,强行向下转型会抛出
ClassCastException(类型转换异常)。为了保证转型安全,通常需要配合
instanceof关键字进行类型判断,先判断父类引用指向的对象是否为目标子类类型,再进行转型。
代码示例
// 先补充子类的特有方法
public class Cat extends Animal {
public Cat(String name) {
super(name);
}
@Override
public void eat() {
System.out.println(this.name + "正在吃猫粮,细嚼慢咽中~");
}
// 猫的特有方法:喵喵叫
public void mew() {
System.out.println(this.name + "喵喵叫,求主人摸摸头~");
}
}
public class Dog extends Animal {
public Dog(String name) {
super(name);
}
@Override
public void eat() {
System.out.println(this.name + "正在啃骨头,嘎嘣脆~");
}
// 狗的特有方法:汪汪叫
public void bark() {
System.out.println(this.name + "汪汪叫,警惕陌生人靠近~");
}
}
// 测试类:演示向下转型与安全判断
public class DownCastTest {
public static void main(String[] args) {
// 1. 先完成向上转型
Animal animal = new Cat("小花猫");
animal.eat(); // 调用Cat重写的eat方法
// 2. 尝试调用子类特有方法(直接调用会报错,父类引用无法访问子类特有方法)
// animal.mew(); // 编译报错
// 3. 向下转型(配合instanceof做安全判断)
if (animal instanceof Cat) {
// 转型成功,还原为Cat类型
Cat cat = (Cat) animal;
// 可以调用Cat的特有方法
cat.mew();
}
// 4. 切换父类引用指向的对象
animal = new Dog("大黄狗");
animal.eat(); // 调用Dog重写的eat方法
// 5. 再次向下转型,判断是否为Dog类型
if (animal instanceof Dog) {
Dog dog = (Dog) animal;
dog.bark();
}
}
}
运行结果
小花猫正在吃猫粮,细嚼慢咽中~
小花猫喵喵叫,求主人摸摸头~
大黄狗正在啃骨头,嘎嘣脆~
大黄狗汪汪叫,警惕陌生人靠近~