多态
概述
多态(Polymorphism)是面向对象三大特性之一,指同一类型的引用在不同运行时可以指向不同子类的对象,调用同一方法时实际执行的是当前对象所属类的实现。通俗说:父类引用指向子类对象,调用方法时看「对象」是谁,就执行谁的方法。多态让程序可以「面向抽象编程」,用统一的父类/接口类型处理多种具体子类型,提高可扩展性和可维护性。
多态建立在 继承 和方法重写之上:子类重写父类方法后,通过父类引用调用该方法时,JVM 会根据实际对象类型(运行时类型)决定执行哪个方法,称为运行时多态(或动态绑定)。
前置建议
已掌握 继承 中的 extends、super 和方法重写(@Override),理解「父类引用可指向子类对象」的写法。
为什么需要多态
- 统一处理多种子类型:用父类类型声明的变量(如
Animal a)可以接收不同子类对象(Dog、Cat),对a调用同一方法即可得到各自的行为,无需写大量 if-else 或 switch。 - 可扩展:新增子类时,原有「面向父类」的代码不必修改,符合开闭原则。
- 解耦:依赖抽象类型(父类/接口)而非具体实现,降低模块间耦合。
多态的三个必要条件
- 继承或实现:存在父类(或接口)与子类(或实现类)关系。
- 方法重写:子类重写了父类中的某方法。
- 父类引用指向子类对象:例如
Animal a = new Dog();,编译时类型是Animal,运行时类型是Dog。
满足以上条件时,通过 a 调用被重写的方法,执行的是子类的实现。
基本语法与概念
编译时类型与运行时类型
Animal a = new Dog("Lucky");- 编译时类型(声明类型):
Animal。编译器只根据Animal检查你能调用哪些方法(即Animal里声明的方法)。 - 运行时类型(实际类型):
Dog。运行时会根据实际对象是Dog来决定执行哪个类的方法实现。
因此:编译看左边(引用类型),运行看右边(对象类型)。
多态下的方法调用
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)
子类对象赋给父类引用,称为向上转型。这是自动、安全的,不需要强转。
Dog d = new Dog("Lucky");
Animal a = d; // 向上转型,等价于 Animal a = new Dog("Lucky");
a.speak(); // 多态:执行 Dog#speak()向上转型后,通过 a 只能调用 Animal 中声明过的成员;子类独有的方法(如 bark())无法通过 a 直接调用。
向下转型(Downcasting)
父类引用转成子类引用,称为向下转型。必须使用强制类型转换,且只有在引用实际指向的确实是该子类(或其子类)对象时才安全,否则运行时会抛出 ClassCastException。
Animal a = new Dog("Lucky");
// a.bark(); // 编译错误:Animal 没有 bark 方法
Dog d = (Dog) a; // 向下转型:已知 a 实际是 Dog,转为 Dog 引用
d.bark(); // 可以调用 Dog 特有方法若实际对象不是 Dog,强转会报错:
Animal a = new Cat("Mimi");
Dog d = (Dog) a; // 运行时 ClassCastException:Cat 不能转为 Dog因此向下转型前应先用 instanceof 判断,再转换。
instanceof 与安全向下转型
instanceof 用于判断引用指向的对象是否是某个类(或该类子类)的实例,避免错误的向下转型。
基本用法
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();
}常见写法:先判断再转型
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:多态统一处理多种子类
用父类数组或集合统一存放不同子类对象,循环中调用同一方法即可表现不同行为。
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:方法参数多态(面向抽象编程)
方法参数使用父类类型,调用方可以传入任意子类对象,方法内部用多态统一处理。
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 再向下转型。
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+ 写法)
}多态与属性、静态方法
- 实例方法:有多态。通过父类引用调用时,执行实际对象所属类的方法。
- 成员变量:没有多态。通过父类引用访问字段时,取的是父类中定义的该字段(子类同名字段会隐藏父类字段,但引用类型决定访问哪一份)。
- 静态方法:没有多态。属于类而非对象,通过引用调用时也是看编译时类型,即父类的静态方法。
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 注解。
相关链接
- 继承 — 方法重写与多态的基础
- 抽象类 — 常作为多态中的「父类」抽象
- 接口 — 接口引用指向实现类,同样体现多态
- Oracle Java 教程 - 多态