Java多态性
多态性的基本概念
多态性是面向对象编程的三大特性之一(封装、继承、多态),指同一个行为具有多个不同表现形式或形态的能力。
方法的多态性
规则:编译看左边,运行看右边
-
编译时:检查左边(父类/接口)是否有该方法
- 如果左边类中没有该方法,编译报错
-
运行时:执行右边(实际子类)的方法实现
- 如果子类重写了该方法,执行子类的方法
- 如果子类没有重写,执行父类的方法
示例代码
class Animal {
public void eat() {
System.out.println("动物吃东西");
}
}
class Cat extends Animal {
@Override
public void eat() {
System.out.println("猫吃鱼");
}
public void catchMouse() {
System.out.println("猫抓老鼠");
}
}
public class Test {
public static void main(String[] args) {
Animal a = new Cat(); // 向上转型
a.eat(); // 输出"猫吃鱼" - 运行看右边(Cat)
// a.catchMouse(); // 编译错误,编译看左边(Animal)没有此方法
}
}
变量的多态性
规则:编译看左边,运行也看左边
- 编译时:检查左边(父类/接口)是否有该变量
- 运行时:访问左边(父类/接口)的变量值
示例代码
class Father {
int x = 10;
}
class Son extends Father {
int x = 20;
}
public class Test {
public static void main(String[] args) {
Father f = new Son();
System.out.println(f.x); // 输出10 - 变量编译和运行都看左边
}
}
总结表格
元素类型 | 编译时检查 | 运行时行为 |
---|---|---|
方法 | 看左边(父类)是否有该方法 | 看右边(子类)的实际实现 |
变量 | 看左边(父类)是否有该变量 | 看左边(父类)的变量值 |
注意事项
- 多态性只适用于实例方法,不适用于静态方法(静态方法编译和运行都看左边)
- 多态性不适用于成员变量(编译和运行都看左边)
- 多态性的实现依赖于方法重写(Override)
- 通过父类引用调用子类特有方法需要向下转型(需先进行instanceof检查)
Java继承与方法调用规则总结
一、问题分析
原代码问题:
Animal animal = new Dog();
animal.printDog(); // 编译错误!
错误原因:
- 编译时类型检查:Java编译器根据引用类型(
Animal
)检查方法是否存在 - 父类引用限制:
Animal
类型不知道Dog
类特有的方法
二、方法类型与调用规则
1. 静态方法 (Static Methods)
class Animal {
static void print() {
System.out.println("Animal static");
}
}
class Dog extends Animal {
static void print() {
System.out.println("Dog static");
}
}
// 调用规则:
Animal.print(); // "Animal static" - 编译时绑定
Dog.print(); // "Dog static" - 编译时绑定
Animal animal = new Dog();
animal.print(); // "Animal static" - 看引用类型!
特点:
- 属于类,不属于对象
- 不参与多态,编译时绑定
- 不能被重写(Override),只能隐藏
2. 实例方法 (Instance Methods)
class Animal {
void print() {
System.out.println("Animal instance");
}
}
class Dog extends Animal {
@Override
void print() {
System.out.println("Dog instance");
}
}
// 调用规则:
Animal animal = new Dog();
animal.print(); // "Dog instance" - 运行时多态
特点:
- 属于对象,参与多态
- 运行时动态绑定
- 可以重写(Override)
三、引用类型与对象类型
规则对比表:
引用类型 | 对象实际类型 | 可调用方法 | 方法绑定 |
---|---|---|---|
Animal |
Animal |
Animal类中的方法 | 静态方法:编译时 实例方法:运行时 |
Animal |
Dog |
Animal类中声明的方法 | 静态方法:看Animal 实例方法:看Dog |
Dog |
Dog |
Dog类中的所有方法 | 正常多态 |
四、解决方案
方案1:使用多态(推荐)
// 在父类中定义通用方法
class Animal {
void print() {
System.out.println("Animal");
}
}
class Dog extends Animal {
@Override
void print() {
System.out.println("Dog");
}
}
// 使用:
Animal animal = new Dog();
animal.print(); // "Dog" - 多态生效
方案2:类型检查与转换
Animal animal = new Dog();
// 安全的方式:
if (animal instanceof Dog) {
Dog dog = (Dog) animal; // 向下转型
dog.printDog(); // 调用子类特有方法
}
// 简洁方式(Java 16+):
if (animal instanceof Dog dog) {
dog.printDog(); // 模式匹配
}
方案3:重新设计类结构
// 如果子类需要有特殊行为,考虑在父类中定义抽象方法
abstract class Animal {
abstract void print();
}
class Dog extends Animal {
@Override
void print() {
System.out.println("Dog");
}
}
五、重要原则
-
编译看左边,运行看右边(实例方法)
- 编译期:检查引用类型是否有该方法
- 运行期:执行实际对象类型的方法
-
静态方法没有多态
- 永远执行引用类型所属类的方法
-
向下转型需要显式检查
- 使用
instanceof
确保类型安全 - 避免
ClassCastException
- 使用
-
设计原则
- 优先使用多态而非类型判断
- 子类应该能够替换父类(里氏替换原则)
六、记忆口诀
父类引用指向子类对象,
实例方法看右边(对象类型),
静态方法看左边(引用类型),
特有方法要转型。