对多态的理解
多态字面意思是“多种形态”。同一个方法调用,实际执行的效果不一样。
父类类型的变量,指向子类对象,调用方法时执行子类的重写版本。
多态需要三个条件:
继承关系
方法重写
父类引用指向子类对象
多态的两种形式
- 向上转型(自动)
父类引用指向子类对象。这样写是自动的,不需要强制转换。
java
Animal animal = new Dog();
这时候animal只能调用父类中有的方法,不能调用子类特有的方法。但调用被重写的方法时,执行的是子类的版本。
- 向下转型(强制)
把父类引用转回子类类型。需要强制转换,而且有风险。
java
Dog dog = (Dog) animal;
向下转型前最好用instanceof判断一下,不然转错类型会报ClassCastException。
代码示例
java
public class Animal {
protected String name;
public Animal(String name) {
this.name = name;
}
public void makeSound() {
System.out.println("动物发出声音");
}
public void eat() {
System.out.println(name + "在吃东西");
}
public void sleep() {
System.out.println(name + "在睡觉");
}
}
public class Dog extends Animal {
private String breed;
public Dog(String name, String breed) {
super(name);
this.breed = breed;
}
@Override
public void makeSound() {
System.out.println(name + "汪汪叫");
}
@Override
public void eat() {
System.out.println(name + "在吃狗粮");
}
public void wagTail() {
System.out.println(name + "摇尾巴");
}
public void fetch() {
System.out.println(name + "叼回了飞盘");
}
}
public class Cat extends Animal {
private String color;
public Cat(String name, String color) {
super(name);
this.color = color;
}
@Override
public void makeSound() {
System.out.println(name + "喵喵叫");
}
@Override
public void eat() {
System.out.println(name + "在吃鱼");
}
@Override
public void sleep() {
System.out.println(name + "缩成一团睡觉");
}
public void climb() {
System.out.println(name + "爬上了树");
}
}
public class Bird extends Animal {
private double wingSpan;
public Bird(String name, double wingSpan) {
super(name);
this.wingSpan = wingSpan;
}
@Override
public void makeSound() {
System.out.println(name + "叽叽喳喳");
}
@Override
public void eat() {
System.out.println(name + "在吃虫子");
}
public void fly() {
System.out.println(name + "飞起来了");
}
}
public class TestPolymorphism {
public static void main(String[] args) {
System.out.println("=== 基本多态演示 ===\n");
Animal a1 = new Dog("旺财", "金毛");
Animal a2 = new Cat("咪咪", "白色");
Animal a3 = new Bird("小飞", 0.5);
a1.makeSound();
a2.makeSound();
a3.makeSound();
a1.eat();
a2.eat();
a3.eat();
System.out.println("\n=== 同一个数组统一处理 ===\n");
Animal[] animals = new Animal[4];
animals[0] = new Dog("小黑", "德牧");
animals[1] = new Cat("花花", "橘色");
animals[2] = new Bird("啾啾", 0.3);
animals[3] = new Dog("大黄", "中华田园犬");
for (Animal animal : animals) {
animal.makeSound();
animal.eat();
animal.sleep();
System.out.println("---");
}
System.out.println("\n=== 向下转型和instanceof ===\n");
Animal unknown = new Dog("来福", "拉布拉多");
if (unknown instanceof Dog) {
Dog d = (Dog) unknown;
d.wagTail();
d.fetch();
}
if (unknown instanceof Cat) {
Cat c = (Cat) unknown;
c.climb();
} else {
System.out.println("来福不是猫,不能调用爬树方法");
}
System.out.println("\n=== 遍历时调用子类特有方法 ===\n");
for (Animal animal : animals) {
if (animal instanceof Dog) {
Dog d = (Dog) animal;
d.wagTail();
} else if (animal instanceof Cat) {
Cat c = (Cat) animal;
c.climb();
} else if (animal instanceof Bird) {
Bird b = (Bird) animal;
b.fly();
}
}
System.out.println("\n=== 多态参数 ===\n");
feedAnimal(a1);
feedAnimal(a2);
feedAnimal(a3);
System.out.println("\n=== 多态返回值 ===\n");
Animal created = createAnimal(1);
created.makeSound();
Animal created2 = createAnimal(2);
created2.makeSound();
}
public static void feedAnimal(Animal animal) {
System.out.print("喂食:");
animal.eat();
}
public static Animal createAnimal(int type) {
if (type == 1) {
return new Dog("新来的狗", "柯基");
} else if (type == 2) {
return new Cat("新来的猫", "黑");
} else {
return new Bird("新来的鸟", 0.4);
}
}
}
今天踩的坑
我写的错误 为什么错 应该怎么写
用父类引用调用子类特有方法 编译时不通过 先向下转型再调用
向下转型不检查instanceof 转错类型会崩溃 转型前用instanceof判断
以为多态对属性也生效 属性没有多态,看声明类型 方法才有多态,属性用getter
把子类对象赋值给父类后忘了本来是什么类型 转型错误 记录类型或用instanceof
在循环里频繁instanceof 代码丑效率低 考虑把方法提取到父类
多态的优缺点
优点:
代码更通用,可以用父类类型写一次逻辑处理所有子类
降低耦合,调用方只依赖父类不依赖具体子类
符合开闭原则,新增子类不需要改现有代码
让代码更简洁,不用写一堆if-else判断类型
缺点:
不能直接调用子类特有的方法
代码阅读起来需要看具体实现才知道执行哪个版本
向下转型有风险
性能上比直接调用稍微慢一点(要动态绑定)
多态中的注意事项
属性没有多态:用父类引用访问属性,拿到的是父类的属性值,不是子类的
静态方法没有多态:静态方法属于类,调用时看引用类型不看对象类型
private方法没有多态:private不能被重写,不存在多态
final方法没有多态:final不能被重写
构造器没有多态
instanceof的使用场景
向下转型前的安全检查
需要调用子类特有方法时
对集合中的元素进行分类处理
java
if (obj instanceof String) {
String s = (String) obj;
}
Java 16之后可以用模式匹配:
java
if (obj instanceof String s) {
System.out.println(s.length());
}
多态参数和多态返回值
多态参数:方法参数写成父类类型,调用时可以传入任何子类对象。这是多态最常用的场景。
多态返回值:方法返回父类类型,实际可以返回任何子类对象。工厂模式常用这种写法。
和重写的关系
多态和重写是绑定的。没有重写就没有多态。多态依赖重写来产生不同的行为。
总结
多态让父类变量指向子类对象,同一个方法调用产生不同效果。核心价值是用统一的代码处理不同类型的数据。加上instanceof做安全检查,向下转型调用子类特有功能。写代码时优先用父类类型声明,需要特殊处理再转型。
