面试官:如何理解 Java 中的多态?
👉 这是一个或许对你有用的社群
🐱 一对一交流/面试小册/简历优化/求职解惑,欢迎加入「芋道快速开发平台」知识星球。下面是星球提供的部分资料:
-
《项目实战(视频)》:从书中学,往事中“练”
-
《互联网高频面试题》:面朝简历学习,春暖花开
-
《架构 x 系统设计》:摧枯拉朽,掌控面试高频场景题
-
《精进 Java 学习指南》:系统学习,互联网主流技术栈
-
《必读 Java 源码专栏》:知其然,知其所以然

👉这是一个或许对你有用的开源项目
国产Star破10w的开源项目,前端包括管理后台、微信小程序,后端支持单体、微服务架构
RBAC权限、数据权限、SaaS多租户、商城、支付、工作流、大屏报表、ERP、CRM、AI大模型、IoT物联网等功能:
多模块:https://gitee.com/zhijiantianya/ruoyi-vue-pro
微服务:https://gitee.com/zhijiantianya/yudao-cloud
视频教程:https://doc.iocoder.cn
【国内首批】支持 JDK17/21+SpringBoot3、JDK8/11+Spring Boot2双版本
这道题在筛选什么人
“如何理解多态”这个问题看起来很基础,但面试官用它做筛选器,能一口气测出四个层次:
-
能不能脱离课本讲清楚 ——“同一个方法调用,因对象不同而表现不同”,能用自己的话说出来就及格了。
-
知不知道底层怎么实现的 ——编译期看声明类型,运行时看实际类型,靠的是虚方法表(vtable)。能说到这一层的人,一般写过不少代码。
-
会不会用多态做设计 ——面向接口编程、策略模式、工厂模式……这些全靠多态撑着。
-
踩没踩过坑 ——属性有没有多态?静态方法呢?
final方法呢?答得出来说明真写过,答不出来说明只是背过。
基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能
项目地址:https://github.com/YunaiV/ruoyi-vue-pro
视频教程:https://doc.iocoder.cn/video/
一句话结论
多态 = 同一个方法签名,运行时根据实际对象类型执行不同的实现。
实现多态需要三个条件,缺一不可:
-
继承或实现 :得有父子关系(类继承或接口实现)。
-
方法重写 :子类必须覆盖父类的方法。
-
向上转型 :用父类引用指向子类对象——
Animal a = new Dog()。
三个条件齐了,调用 a.makeSound() 时,JVM 才会在运行时去找 Dog 的实现,而不是 Animal 的。
基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能
项目地址:https://github.com/YunaiV/yudao-cloud
视频教程:https://doc.iocoder.cn/video/
JVM 怎么做到“同一行代码调不同方法”的
两步走,各管各的:
编译期——静态检查
编译器只看引用变量的声明类型。你写了 Animal a,编译器就只检查 Animal 类里有没有 makeSound() 方法、访问权限对不对。它完全不关心 a 到底指向哪个子类对象。
运行时——动态派发
JVM 拿到实际对象后,通过虚方法表(vtable) 找到该对象类型中 makeSound() 的真实入口地址。虚方法表是每个类在加载时就建好的,里面按序存着所有可被重写的方法的实际地址。子类重写了哪个方法,对应位置就替换成子类的实现。
这就是“编译看左边,运行看右边”的本质 ——左边决定你能调什么,右边决定实际执行什么。
一段代码讲透多态的全部要素
interface Animal {
void makeSound();
}
class Dog implements Animal {
@Override
public void makeSound() {
System.out.println("Woof!");
}
}
class Cat implements Animal {
@Override
public void makeSound() {
System.out.println("Meow!");
}
}
publicclass Demo {
public static void main(String[] args) {
Animal a;
a = new Dog();
a.makeSound(); // Woof! —— 运行时绑定到 Dog
a = new Cat();
a.makeSound(); // Meow! —— 同一个引用,行为完全不同
// 多态的杀手级应用:集合 + 循环
Animal[] animals = {new Dog(), new Cat()};
for (Animal animal : animals) {
animal.makeSound(); // 不需要 if-else 判断类型,新增动物只要实现接口
}
}
}
重点不是这段代码本身,而是最后那个 for 循环。 未来新增一个 Bird 类,只要实现 Animal 接口,循环体一行代码不用改——这就是多态带来的开闭原则 :对扩展开放,对修改关闭。
重写 vs 重载:一个纵向一个横向,别混了
|
维度 |
重写(Override) |
重载(Overload) |
|---|---|---|
| 发生时机 |
运行时 |
编译时 |
| 发生位置 |
父类与子类之间 |
同一个类内部 |
| 方法签名 |
必须相同 |
名字相同,参数必须不同 |
| 本质 |
纵向——子类改变父类的行为 |
横向——同一类提供多种参数选择 |
| 与多态的关系 |
多态的核心机制 |
和多态无关,只是语法便利 |
面试时常见的坑:面试官问“重载算不算多态?”答案是不算。 重载在编译期就确定了调哪个方法,不存在“运行时动态派发”。
多态的正确打开方式和常见翻车现场
正确打开方式:
-
面向接口编程 ——声明类型用接口或抽象类,具体实现交给子类。Spring 的依赖注入本质上就是在玩多态。
-
遵守里氏替换原则(LSP) ——子类必须能完全替代父类,不能偷偷改变父类方法的预期行为。你重写
compareTo()时把大小关系反过来,调用方的代码就全废了。 -
善用设计模式 ——策略模式(运行时切换算法)、模板方法模式(父类定骨架,子类填细节)、工厂模式(返回接口类型,隐藏创建细节)——全靠多态支撑。
翻车现场:
1. 属性没有多态
class Parent { String name = "Parent"; }
class Child extends Parent { String name = "Child"; }
Parent p = new Child();
System.out.println(p.name); // 输出 "Parent",不是 "Child"!
成员变量的访问在编译期就定了,看声明类型,不看实际类型。这和方法的动态绑定完全不同。
2. 静态方法没有多态
static 方法属于类而不是对象,调用时看引用变量的声明类型。所以父类引用调静态方法,永远调的是父类的版本——这叫“隐藏”,不叫“重写”。
3. private/final 方法不参与多态
private 方法对子类不可见,final 方法禁止重写。没有重写就没有多态,这两种方法的调用在编译期就完全确定了。构造器同理。
欢迎加入我的知识星球,全面提升技术能力。
👉 加入方式,“长按”或“扫描”下方二维码噢:

星球的内容包括:项目实战、面试招聘、源码解析、学习路线。





文章有帮助的话,在看,转发吧。
谢谢支持哟 (*^__^*)
更多推荐



所有评论(0)