静态分派发生在编译期,编译器仅根据变量声明类型和实参静态类型选择重载方法,与对象实际类型无关,零运行时开销,但受泛型擦除和重写/重载混淆等限制。

静态分派发生在编译期,不是运行时
Java 的重载(Overload)决议完全由编译器在编译阶段完成,和对象实际类型无关。哪怕你写 obj.method("hello"),编译器只看 obj 的**声明类型**(即变量类型),再结合实参的**静态类型**(比如字面量类型、变量声明类型),从当前作用域中所有同名但参数签名不同的方法里挑一个最匹配的。
常见错误现象:NullPointerException 不会阻止重载决议;obj 为 null 时,只要声明类型有对应方法,编译照样通过——因为根本没走到运行时。
- 使用场景:多态接口暴露不同参数组合的便捷入口,比如
log(String)和log(String, Throwable) - 参数差异:编译器按「精确匹配 → 自动装箱/拆箱 → 基本类型提升(如
int→long)→ 可变参数」顺序尝试匹配 - 性能影响:零运行时开销,纯编译期查表
泛型擦除让重载容易“撞车”
泛型信息在编译后被擦除,导致 List<string></string> 和 List<integer></integer> 都变成原始类型 List。如果两个方法仅靠泛型参数区分,比如 foo(List<string>)</string> 和 foo(List<integer>)</integer>,编译器会报错:method foo(List) is already defined。
这不是重载失败,是根本无法定义——JVM 层面不认泛型,方法签名只剩原始类型。
立即学习“Java免费学习笔记(深入)”;
- 解决办法:改用不同方法名,或加一个无意义但类型不同的参数(如
foo(List<string>, Void)</string>和foo(List<integer>, Class)</integer>) - 注意:泛型类内部的重载不受影响,因为擦除发生在生成字节码前,编译器仍能靠源码中的泛型做静态判断
- 兼容性风险:Java 8 之前对泛型推导更保守,某些嵌套调用可能意外触发更宽泛的匹配
子类重写父类方法时,重载和重写的边界容易混淆
重载(Overload)和重写(Override)共存时,编译器先做静态分派选重载版本,再由 JVM 在运行时按实际类型决定是否走重写逻辑。但很多人误以为「子类加了个参数不同的同名方法,就算重写了父类方法」。
典型错误现象:父类有 process(Object),子类加了 process(String),结果调用 child.process(new Object()) 仍然走父类方法,而 child.process("abc") 才走子类——这其实是两个独立的重载方法,不是重写。
- 关键判断:重写要求方法名、参数列表、返回类型(协变除外)完全一致;重载只要求方法名相同、参数列表不同
- 编译器视角:它看到的是变量声明类型,比如
Parent p = new Child(),调用p.process("x")时,只查Parent类里有没有接受String的process方法 - 容易踩的坑:IDE 高亮「override」提示可能误导人,得看字节码或反编译确认是否真生成了
invokespecial指令
字符串字面量触发 String 重载优先级高于 Object
Java 对字符串字面量有特殊处理。当你传 "hello" 这种字面量,编译器会优先匹配接收 String 的重载,而不是更宽泛的 Object 或 CharSequence,哪怕后者在继承链上更“通用”。
示例:void m(String s) 和 void m(Object o) 同时存在,m("test") 一定调 String 版本;但 Object o = "test"; m(o) 就走 Object 版本——因为变量 o 的静态类型是 Object。
- 为什么这样做:避免开发者每次传字符串都要显式强转,提升 API 友好度
- 陷阱:如果还定义了
m(CharSequence),而String实现了CharSequence,编译器仍选String版本(更具体),不会退到接口版 - 兼容性注意:Java 14+ 对文本块(
"""...""")也按String处理,行为一致








