重载是编译期静态分派,依据参数静态类型确定方法签名;重写是运行期动态分派,依据对象实际类型查虚方法表找具体实现;二者均以方法名+参数类型列表为唯一签名判定依据。

重载和重写在 JVM 层面根本不是一回事:一个是编译期靠静态类型决定的“选方法”,一个是运行期靠实际类型决定的“找实现”。搞不清这点,就永远在背口诀。
重载是编译期静态分派,靠参数的静态类型匹配
Java 编译器在生成字节码时,就已经把重载方法调用固定到某一个具体版本上。它只看变量声明类型(即静态类型),不关心 new 出来的是什么对象。
- 比如 Human man = new Man();,调用 sayHello(man),编译器只看到参数是 Human 类型,就直接绑定到 sayHello(Human) 这个方法签名上
- 即使你后面把 man 强转成 (Man)man,编译器也只看括号里写的类型,从而选择 sayHello(Man)
- 这个过程发生在编译阶段,生成的字节码里直接就是 invokevirtual sayHello(LHuman;)V 或 invokevirtual sayHello(LMan;)V,没有运行时判断
重写是运行期动态分派,靠对象的实际类型查虚方法表
当执行 a.speak()(其中 Animal a = new Dog())时,JVM 不是在调用前就知道该进哪个方法体,而是在运行时查对象头里的类元信息,再定位到该类的虚方法表(vtable),从中取出 speak() 对应的真实入口地址。
- 每个类在加载时都会构建自己的虚方法表,表中每一项对应一个可被重写的方法名+签名,填入当前类中最终实现的字节码地址
- Dog 类的 vtable 中,speak() 指向的是 Dog 的方法体;Animal 类的 vtable 中则指向 Animal 的方法体
- JVM 执行 invokevirtual 指令时,先通过引用拿到对象实例,再从实例获取其实际类(Dog),再查该类 vtable,完成动态绑定
方法签名是 JVM 区分重载与重写的唯一语法锚点
无论重载还是重写,“方法签名”都指 方法名 + 参数类型列表,返回类型、修饰符、异常都不参与签名判定。
- 重载要求签名不同——否则编译失败,因为编译器无法在调用点唯一确定目标方法
- 重写要求签名完全相同——否则不算重写,而是定义了一个新方法(可能造成逻辑错误却无编译告警)
- JVM 字节码层面其实允许返回类型不同(描述符含返回类型),但 Java 源码层禁止,这是语言规范对 JVM 能力的主动约束
五条调用指令暴露了 JVM 的分派逻辑
Java 虚拟机用不同指令区分调用场景,背后是绑定时机与机制的根本差异:
- invokestatic:静态方法,编译期解析,无多态
- invokespecial:构造器、私有方法、父类方法,也是编译期解析,禁止重写干扰
- invokevirtual:实例方法,默认虚方法调用,触发动态分派(重写生效处)
- invokeinterface:接口方法,运行期查实现类的 vtable,也是动态分派
- invokedynamic:Lambda 和方法句柄专用,分派逻辑由引导方法控制,彻底脱离 JVM 固化逻辑
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











