👉 这是一个或许对你有用的社群

🐱 一对一交流/面试小册/简历优化/求职解惑,欢迎加入「芋道快速开发平台」知识星球。下面是星球提供的部分资料: 

👉这是一个或许对你有用的开源项目

国产Star破10w的开源项目,前端包括管理后台、微信小程序,后端支持单体、微服务架构

RBAC权限、数据权限、SaaS多租户、商城、支付、工作流、大屏报表、ERP、CRMAI大模型、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双版本 


这道题在筛选什么人

“如何理解多态”这个问题看起来很基础,但面试官用它做筛选器,能一口气测出四个层次:

  1. 能不能脱离课本讲清楚 ——“同一个方法调用,因对象不同而表现不同”,能用自己的话说出来就及格了。

  2. 知不知道底层怎么实现的 ——编译期看声明类型,运行时看实际类型,靠的是虚方法表(vtable)。能说到这一层的人,一般写过不少代码。

  3. 会不会用多态做设计 ——面向接口编程、策略模式、工厂模式……这些全靠多态撑着。

  4. 踩没踩过坑 ——属性有没有多态?静态方法呢?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)

发生时机

运行时

编译时

发生位置

父类与子类之间

同一个类内部

方法签名

必须相同

名字相同,参数必须不同

本质

纵向——子类改变父类的行为

横向——同一类提供多种参数选择

与多态的关系

多态的核心机制

和多态无关,只是语法便利

面试时常见的坑:面试官问“重载算不算多态?”答案是不算。 重载在编译期就确定了调哪个方法,不存在“运行时动态派发”。

多态的正确打开方式和常见翻车现场

正确打开方式:

  1. 面向接口编程 ——声明类型用接口或抽象类,具体实现交给子类。Spring 的依赖注入本质上就是在玩多态。

  2. 遵守里氏替换原则(LSP) ——子类必须能完全替代父类,不能偷偷改变父类方法的预期行为。你重写 compareTo() 时把大小关系反过来,调用方的代码就全废了。

  3. 善用设计模式 ——策略模式(运行时切换算法)、模板方法模式(父类定骨架,子类填细节)、工厂模式(返回接口类型,隐藏创建细节)——全靠多态支撑。

翻车现场:

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 方法禁止重写。没有重写就没有多态,这两种方法的调用在编译期就完全确定了。构造器同理。


欢迎加入我的知识星球,全面提升技术能力。

👉 加入方式,长按”或“扫描”下方二维码噢

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

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

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐