重载在编译阶段起作用,javac根据变量声明类型和参数的字面量/类型/个数/顺序静态绑定方法调用,与运行时对象实际类型无关。

重载(Overload)到底在哪个阶段起作用?
重载是编译时多态,也就是说,javac 在编译那一刻就决定了调用哪个方法——它只看**变量声明类型 + 实际传入参数的字面量/类型/个数/顺序**,和运行时对象的实际类型无关。
常见错误现象:
写了一个 print(Object o) 和 print(String s),结果传入 null 却调用了 Object 版本——因为 null 没有类型,编译器选最宽泛的匹配项。
- 必须定义在同一个类中(或父类+子类同名方法也算“可见重载”,但本质仍是编译期绑定)
- 参数列表不同:类型不同、个数不同、顺序不同(注意:仅参数名不同不算,
int a, int b和int b, int a是非法重载) - 返回值类型、异常、访问修饰符全都不影响重载判断——它们甚至可以完全不一样
- JDK 中
System.out.println()就是典型:几十个重载版本,你写println(42)或println(new Date()),编译器直接挑对的
重写(Override)为什么总在运行时才决定调用谁?
重写是运行时多态,核心在于 JVM 会根据**对象实际创建的类型**(而非引用变量声明的类型)去查虚方法表(vtable),从而调用正确的实现。这就是所谓“动态绑定”。
容易踩的坑:
加了 @Override 却编译不报错,但实际没重写成功——比如父类方法是 private void run(),子类写 @Override public void run() 看似合理,实则只是个新方法,跟父类毫无关系。
立即学习“Java免费学习笔记(深入)”;
- 必须有继承关系;父类方法不能是
private、static或final - 方法名、参数列表必须严格一致;返回类型可以是父类返回类型的子类(协变返回,如
Number→Integer) - 子类方法访问权限不能更小(
protected可重写public,但反过来不行) - 检查异常只能缩小不能扩大:父类抛
IOException,子类只能抛FileNotFoundException这类子类,不能抛Exception
为什么不能靠返回值区分重载?
因为调用方可能根本不在乎返回值。例如 add(1, 2),如果同时存在 int add(int, int) 和 String add(int, int),编译器无法从调用语句本身判断你要的是哪个——没有上下文返回类型约束。
这种设计不是缺陷,而是刻意为之的保守性:避免因返回值推导失败导致编译歧义或隐式转换风险。
- Java 编译器只依据「方法签名」(方法名 + 参数类型)做重载解析
- 哪怕两个方法返回
void和int,只要参数一样,就是非法重载,编译直接报错 - 想靠返回值分流?用不同方法名,或者封装成泛型工具类(如
Utils.toInt(...)/Utils.toString(...))
多态性真正生效的临界点在哪?
多态只有在「父类引用指向子类对象」且「调用被重写的方法」时才体现出来。比如 Animal a = new Dog(); a.makeSound(); ——此时编译看的是 Animal 类型,运行看的是 Dog 实例。
重载永远不参与这个过程:哪怕你写 a.print("hi"),也只按 Animal 类里有没有匹配的 print(String) 来静态绑定,不会因为 a 实际是 Dog 就去查 Dog 的重载版本。
- 重载是“一个类里多个入口”,重写是“父子之间同一入口的不同实现”
- 混淆两者的典型症状:以为加了
@Override就能触发多态,结果发现子类方法压根没被调用——先检查是否真构成重写(继承链、访问权限、方法签名) - 调试建议:在父类和子类方法里都加日志,打印
this.getClass().getSimpleName(),一眼看清运行时调用链








