重载发生在同一类中,由编译器根据参数个数、类型、顺序在编译期静态绑定;重写发生在父子类间,由JVM根据对象实际类型在运行期动态绑定,遵循签名、返回类型、异常等契约。

重载(Overload)发生在同一个类里,靠参数列表区分
重载是编译期决定的:编译器看调用时传了什么类型、几个参数,就去匹配对应的方法签名。返回值类型、访问修饰符、异常声明都不参与区分。
常见错误现象:javac 报错 method xxx is already defined,其实是你写了两个参数列表完全一样的方法(比如都写成 void foo(String s)),这时候不是重载,是重复定义。
- 必须在同一个类中;跨类不算重载
- 参数个数、类型、顺序任一不同即可(如
foo(int)和foo(Integer)是两个重载,但注意自动装箱可能让调用歧义) -
foo(String...)和foo(String[])不能共存——编译器认为它们签名相同 - 泛型擦除后若签名一致(如
process(List<String>)和process(List<Integer>)),编译不通过
重写(Override)发生在父子类之间,必须满足签名+返回+异常契约
重写是运行期多态的基础:JVM 在运行时根据对象实际类型,查虚方法表(vtable)跳转到子类实现。它不是“覆盖”,而是“提供新实现”。
典型错误现象:父类方法是 private 或 static,子类写了同名方法,结果发现没被调用——这不是重写,只是定义了一个新方法。
立即学习“Java免费学习笔记(深入)”;
- 子类方法名、参数列表、返回类型(协变返回允许子类型,如父类返回
Object,子类可返回String)必须一致 - 子类访问修饰符不能比父类更严格(
protected可重写public,但不能反过来) - 子类不能抛出比父类更宽泛的检查异常(
Exception不能重写只抛IOException的方法) - 加
@Override注解不是可选,是必须——它能帮你提前发现拼写错误或继承关系断裂
编译期多态靠重载,运行期多态靠重写,混用会掉进陷阱
很多人以为“重载也能实现多态”,其实只是假象:重载选择在编译期固化,哪怕你把变量声明为父类类型,只要传参确定,就选定了具体方法。
示例:List<String> list = new ArrayList<>(); 调用 list.add("x"),走的是 add(E);但如果重载一个 add(String) 在同一类里,而你用 ArrayList 实例调用,编译器优先选 add(String) —— 这和多态无关,纯属静态解析。
- 重载方法调用目标在编译时锁定,和对象运行时类型无关
- 重写方法调用目标在运行时才确定,哪怕变量是
Animal a = new Dog(); a.speak();,也会执行Dog.speak() - 如果父类有重载,子类又重写其中某个,容易误以为所有重载都被“继承并重写”了——其实只有被重写的方法生效,其他重载仍走父类逻辑
泛型、lambda、桥接方法会让重写判断更隐蔽
泛型擦除后生成的桥接方法(bridge method)常被忽略。比如你在子类重写 <T> T get(),编译器会自动生成一个 Object get() 桥接方法来满足 JVM 对重写的字节码要求。它不可见,但真实存在。
另一个坑:lambda 表达式作为函数式接口实例时,其 toString() 或 equals() 行为取决于是否重写了这些方法——但默认继承自 Object,不会触发重写逻辑,除非显式在实现类中 override。
- 用
javap -c看字节码,能确认是否真生成了桥接方法 - IDE 的 “Find Usages” 可能只标出源码里的重写方法,漏掉桥接方法的调用点
- 反射获取
Method时,桥接方法的isBridge() == true,别把它当普通方法处理







