重载(Overload)和重写(Override)的区别?

2025年 阅读约 8 分钟 面试指南 · Java面试

深入解析Java中重载和重写的区别,从编译期到运行期,分初级、中级、高级三个层次全面讲解。

一句话总结

重载是编译时多态(静态分派,同方法名不同参数,看静态类型)→ 重写是运行时多态(动态分派,子类覆盖父类方法,看实际类型)。重写规则:访问权限不能降低、异常不能扩大、static/final/private 方法不能被重写。JVM 通过 vtable(虚方法表)实现动态分派。

初级理解

重载(Overload)— 编译时多态:同一个类中,方法名相同但参数列表不同(参数个数、类型、顺序不同)。返回值类型可以不同,但不能仅靠返回值区分重载。发生在编译期

重写(Override)— 运行时多态:子类重新定义父类的方法,方法名、参数列表、返回值类型必须完全相同(JDK 5+ 允许协变返回类型)。发生在运行期

对比维度重载(Overload)重写(Override)
发生位置同一个类中父子类之间
参数列表必须不同必须相同
返回类型可以不同相同或协变
访问修饰符可以任意不能比父类更严格
异常可以任意不能抛出更宽泛的异常
绑定时机编译期(静态绑定)运行期(动态绑定)
// 重载示例 class Calculator { public int add(int a, int b) { return a + b; } public double add(double a, double b) { return a + b; } public int add(int a, int b, int c) { return a + b + c; } } // 重写示例 class Animal { public void sound() { System.out.println("Animal sound"); } } class Dog extends Animal { @Override // 注解,编译器检查是否正确重写 public void sound() { System.out.println("Woof!"); } }

中级深入

重写的规则细节:

1. 访问权限不能降低:父类方法是 public,子类不能改成 protected 或 private。

2. 异常不能扩大:子类抛出的受检异常不能比父类更宽泛(可以是父类异常的子类或不抛出)。

3. static 方法不能被重写:static 方法属于类而非实例,子类的同名 static 方法是"隐藏"而非重写。

4. final 方法不能被重写:final 方法禁止子类修改。

5. private 方法不能被重写:private 方法对子类不可见,子类同名方法是全新的方法。

重载的优先级(方法调用时的选择顺序):

public class OverloadPriority { public void test(Object o) { System.out.println("Object"); } public void test(String s) { System.out.println("String"); } // public void test(Integer i) { System.out.println("Integer"); } public static void main(String[] args) { OverloadPriority op = new OverloadPriority(); op.test(null); // 输出 "String" // 编译器会选择最具体的类型:String 比 Object 更具体 } }

编译器选择重载方法的优先级:精确匹配 → 自动装箱 → 可变参数 → 父类/接口。如果同时有多个匹配且无法判断哪个更具体,编译器会报"ambiguous"错误。

高级拓展

静态分派与动态分派:

静态分派(Static Dispatch)— 重载:编译器根据静态类型(变量声明的类型)决定调用哪个重载方法。发生在编译期。

动态分派(Dynamic Dispatch)— 重写:JVM 根据实际类型(对象运行时的类型)决定调用哪个重写方法。通过方法表(vtable)实现,发生在运行期。

// 静态分派 vs 动态分派 class Parent {} class Child extends Parent {} public class DispatchDemo { void overload(Parent p) { System.out.println("Parent"); } void overload(Child c) { System.out.println("Child"); } public static void main(String[] args) { DispatchDemo demo = new DispatchDemo(); Parent obj = new Child(); // 静态类型 Parent,实际类型 Child demo.overload(obj); // 输出 "Parent" — 静态分派,看静态类型 // 如果 overload(Child) 不存在,会输出 "Parent" } }

方法表(vtable)机制:每个类在方法区中有一张虚方法表,记录了该类所有虚方法(可被重写的方法)的实际入口地址。子类重写父类方法时,会覆盖方法表中对应的条目。JVM 通过 invokevirtual 指令查表实现动态分派。

桥接方法(Bridge Method):当子类重写父类方法时使用了协变返回类型,编译器会自动生成一个桥接方法来保持字节码兼容性。

// Java 源码 class Parent { public Parent getInstance() { return this; } } class Child extends Parent { @Override public Child getInstance() { return this; } // 协变返回类型 } // 编译后,Child 类实际有两个方法: // 1. public Child getInstance() — 我们写的 // 2. public Parent getInstance() — 编译器生成的桥接方法,内部调用方法1
面试加分项:能说出"重载是静态分派、重写是动态分派",并解释方法表的实现原理,说明你对 JVM 底层有深入理解。

实战场景

场景:重载的自动装箱陷阱

// 陷阱:自动装箱导致调用非预期的重载方法 public class OverloadTrap { public void remove(int index) { System.out.println("按索引删除: " + index); } public void remove(Integer obj) { System.out.println("按对象删除: " + obj); } public static void main(String[] args) { OverloadTrap t = new OverloadTrap(); t.remove(1); // "按索引删除: 1" t.remove(Integer.valueOf(1)); // "按对象删除: 1" // ArrayList 就有这个坑:remove(int) 按索引,remove(Object) 按元素 } }

场景:@Override 注解的价值

// 反例:以为在重写,实际是重载 class Parent { public void doSomething(String name) { } } class Child extends Parent { // 拼写错误或参数不同 → 变成重载而非重写! public void doSomethng(String name) { } // 编译通过但不重写 } // 正例:加 @Override,编译器帮你检查 class Child extends Parent { @Override public void doSomethng(String name) { } // 编译错误!方法名拼错 }

面试模拟

Q:重载和重写的核心区别是什么?

A:1) 重载在同一个类中,重写在父子类之间;2) 重载看参数列表不同,重写看方法签名相同;3) 重载是编译期静态分派,重写是运行期动态分派;4) 重载与返回值/异常无关,重写有严格限制(权限不降低、异常不扩大)。

Q:什么是桥接方法(Bridge Method)?

A:当子类重写父类方法使用协变返回类型时,编译器自动生成一个桥接方法。例如父类返回 Parent,子类返回 Child,编译器生成一个返回 Parent 的桥接方法,内部调用子类的 Child 版本。这是为了保持字节码层面的方法签名兼容性。