Day13 | Java多态详解
在上一篇文章Day12 | Java继承详解中,我们讲了一下"继承"怎么让代码复用变得简单。
今天我们来看一下"多态"怎么让代码更具有灵活性和扩展性。
多态也是面向对象三大特性中的核心特性之一。
一、多态是什么
多态字面理解就是多种形态。在Java中,它允许我们使用父类类型的引用,来指向子类对象,并根据实际对象类型来调用方法。
还是来看我们经常使用的动物类的案例:
package com.lazy.snail.day13;/*** @ClassName Animal* @Description TODO* @Author lazysnail* @Date 2025/5/28 10:26* @Version 1.0*/
public class Animal {public void shout() {System.out.println("动物叫");}
}
父类Animal定义了一个shout方法。
package com.lazy.snail.day13;/*** @ClassName Dog* @Description TODO* @Author lazysnail* @Date 2025/5/28 10:26* @Version 1.0*/
public class Dog extends Animal {@Overridepublic void shout() {System.out.println("狗叫:汪汪汪");}
}package com.lazy.snail.day13;/*** @ClassName Cat* @Description TODO* @Author lazysnail* @Date 2025/5/28 10:27* @Version 1.0*/
public class Cat extends Animal {@Overridepublic void shout() {System.out.println("猫叫:喵喵");}
}
Cat类和Dog都通过extends关键字继承Animal类。
并且重写(@Override)了Animal中的shout方法。
package com.lazy.snail.day13;/*** @ClassName Day13Demo* @Description TODO* @Author lazysnail* @Date 2025/5/28 10:27* @Version 1.0*/
public class Day13Demo {public static void main(String[] args) {Animal a1 = new Dog();Animal a2 = new Cat();a1.shout();a2.shout();}
}
在测试Demo中,我们并没有使用Dog dog = new Dog();和Cat cat = new Cat();创建猫狗对象。
而是直接使用了父类Animal指向了创建的子类猫狗的对象。
当使用对象.方法时,调用了子类对象的方法。
方法调用看对象实际类型,而不是变量声明类型。
二、多态的前提条件
整理了一下多态的一些前提条件:
条件 | 说明 |
---|---|
继承 | 必须有父类与子类关系 |
方法重写 | 子类必须重写父类的方法 |
父类引用指向子类对象 | Animal a = new Dog(); |
根据上面的案例:
1、UML类图,Cat和Dog类均继承Animal
2、Cat和Dog类均重写了父类Animal中的shout方法
3、Demo中创建对象时,均使用父类Animal指向子类对象
三、核心原理与引用类型转换
1、编译看左边,运行看右边
Animal a = new Dog();
a.shout();
编译阶段只看左边(Animal):只要Animal有shout方法,编译就通过;
运行阶段看右边(Dog):实际调用的是Dog的shout方法。
方法调用是动态绑定:运行时决定
如果把Animal中的shout方法注释(IDEA多行注释:选中代码块,Ctrl + Shift + /)
Demo中就会出现编译错误:
明确的指出了编译阶段,没办法在Animal中找到shout方法。
如果把Dog类中的shout方法注释(记得把Animal中shout方法的注释取消)。
再运行Demo,会看到dog对象调用shout方法后输出的是“动物叫”。
2、向上转型
Animal a = new Dog(); // 自动进行,不需要强转
安全、常见,是多态的基础。
但只能调用父类声明过的方法(即使子类重写了也可调用)
3、向下转型
Animal a = new Dog();
Dog d = (Dog) a; // 强制类型转换 d.shout();
有风险,可能ClassCastException,最好使用instanceof判断:
if (a instanceof Dog) {Dog d = (Dog) a;d.shout();
}
小结一下:
操作 | 方向 | 安全性 | 用途 |
---|---|---|---|
向上转型 | 子 → 父 | 安全、自动 | 支持多态统一编程 |
向下转型 | 父 → 子 | 有风险、需强转 | 使用子类特有方法或属性 |
四、多态的变量访问
多态下变量的访问是静态绑定的,也就是只看编译时的类型,不会动态派发。
class Animal {String name = "动物";
}class Dog extends Animal {String name = "狗";
}public class Demo {public static void main(String[] args) {Animal a = new Dog();System.out.println(a.name);}
}
父子类中都定义了name属性。
Animal a =newDog();满足多态的条件,a.name输出的是“动物”。
编译阶段的类型是Animal,绑定的就是Animal中的name属性(看左边的类型)。
五、虚方法调用机制
Java中除了static、private、final修饰的方法外,其它方法都被视为虚方法,即运行时动态绑定。
当你写:
Animal a = new Dog();
a.shout();
JVM实际会在运行时根据对象的类型,从它的虚方法表中查找对应实现,然后调用。
后续讲到JVM的时候,会继续讲
六、多态应用场景
1、接口与抽象类的统一编程
public void startAnimal(Animal a) {a.shout(); // 不关心具体类型,统一接口
}
方法可以接受任意Animal子类,如Dog、Cat、Tiger等等。
2、面试常见:方法调用顺序
class A {public void show() {System.out.println("A.show()");}
}
class B extends A {public void show() {System.out.println("B.show()");}
}
class C extends B {public void show() {System.out.println("C.show()");}
}public class Test {public static void main(String[] args) {A obj = new C();obj.show();}
}
看看这个案例,理一下,脑跑一下代码,main方法中的obj.show会输出什么?
结语
小结下一本文关于多态的知识点:
概念 | 要点 |
---|---|
多态 | 同一父类引用,调用不同子类的方法实现 |
条件 | 继承 + 方法重写 + 向上转型 |
编译 vs 运行 | 编译看变量类型,运行看对象实际类型 |
动态绑定 | 运行时确定方法实现,提高灵活性 |
变量访问 | 静态绑定,编译期确定 |
多态实际就是屏蔽具体实现差异,统一对外接口。 在后续的代码实践中,我们会在使用多态的地方继续了解多态的魅力。
下一篇预告
Day14 | 抽象类和接口
如果你觉得这系列文章对你有帮助,欢迎关注专栏,我们一起坚持下去!
本文首发于知乎专栏《Java 100天成长计划》