面试官问:重载和重写有什么区别?(附图解+比喻+避坑指南)
📝 摘要:重载是编译期静态绑定(同一类,参数不同),重写是运行期动态绑定(子类覆盖父类方法,参数必须一致)。本文用“点奶茶”生活化比喻 + 全方位对比表 + 字节码底层验证 + 接口重写规则 + 高频面试追问 + 全网最全避坑清单,彻底吃透Java多态核心考点。一句话:重载看参数、编译绑定;重写看对象、运行绑定。
文章目录
💬 面试还原
面试官:重载(Overload)和重写(Override)有什么区别?
这是Java基础面试出场率TOP1的对比题。初级开发者只会背“参数不同、子类覆盖”,中高级面试重点考察绑定时机、底层JVM机制、边界坑点。
今天用一张全景图、一个生活化比喻、字节码实证、五道高频追问、实战坑点,帮你彻底吃透这道题,面试直接满分作答。
✅ 满分一句话总结
重载看参数、编译绑定;重写看对象、运行绑定。
✅ 核心本质
重载:编译期静态绑定,提前锁死调用方法
重写:运行期动态绑定,根据实际对象执行方法
✅ 背诵口诀(面试极速版)
参数不同叫重载,子类覆写叫重写;编译静态定死调用,运行动态看对象。
🧠 一图看懂:重载 vs 重写 全景对比

🍵 生活化比喻:零基础秒懂
1、重载 = 同一种服务,多种入参方式
奶茶店点单:同样是「做奶茶」
- 点单(珍珠) → 珍珠奶茶
- 点单(椰果) → 椰果奶茶
- 点单(布丁、少糖) → 布丁少糖奶茶
特点:你开口的一瞬间(编译期),店员就知道做哪一款,完全固定,不会变。
2、重写 = 同一指令,不同对象不同实现
父子俩都会「做奶茶」:
- 父亲做奶茶:多糖、高温
- 儿子重写方法:少糖、常温
特点:指令名一样,只有拿到具体的人(运行时对象),才知道实际怎么做。
3、静态方法隐藏 = 店铺招牌不属于个人
招牌是店铺的(属于类),不是师傅的(不属于对象),所以无法动态替换,只能覆盖隐藏。

📊面试核心对比表(面试速查)
| 维度 | 重载(Overload) | 重写(Override) |
|---|---|---|
| 发生范围 | 当前类内部 | 子父类、实现类与接口 |
| 核心本质 | 参数差异化适配 | 行为个性化覆写 |
| 绑定时机 | 编译期静态绑定 | 运行期动态绑定 |
| 方法名 | 必须一致 | 必须一致 |
| 参数列表 | 必须不同 | 必须完全相同 |
| 返回值 | 任意 | 相同 / 协变子类 |
| 访问权限 | 任意 | 只能更宽松,不能更严格 |
| 异常声明 | 任意 | 只能缩小、不能扩大 |
| 静态方法 | 支持重载 | 不支持(只能隐藏) |
| final方法 | 支持重载 | 禁止重写 |
| private方法 | 支持重载 | 禁止重写(对子类不可见) |
| 构造方法 | 只能重载 | 完全不能重写 |
| 多态归属 | 非多态 | Java唯一运行时多态 |
🔬 字节码底层实证
1、重载:编译期锁定方法签名
很多教程误区:重载 = invokevirtual,错误。
重载的本质:编译器根据参数列表,编译阶段直接确定目标方法签名,和字节码指令无关:
-
static 方法重载 →
invokestatic -
private 方法重载 →
invokespecial -
普通实例方法重载 →
invokevirtual
核心结论:无论哪种指令,重载调用目标编译期永久固定。
2、重写:运行期虚方法表动态分派
重写编译后字节码永远指向父类方法,编译期不确定最终执行逻辑。
程序运行时,JVM 通过当前对象的虚方法表 vtable,查找子类覆写后的方法执行,实现动态多态。
🏛️ 接口方法实现 = 标准重写
很多面试官会追问:“实现接口中的抽象方法算不算重写?”
答案是:算! 实现类重写接口抽象方法、默认方法,全部属于重写,遵循重写全套规则。
// 接口定义
interface Flyable {
void fly(); // 默认 public abstract
default void glide() {
System.out.println("默认滑翔");
}
}
// 实现类:实现接口方法也是重写
class Bird implements Flyable {
@Override
public void fly() { // 实现抽象方法——这算重写
System.out.println("鸟儿飞行");
}
@Override
public void glide() { // 重写接口默认方法——也属于重写
System.out.println("鸟儿滑翔");
}
}
重点注意:接口方法重写的特殊规则
- 访问修饰符必须用
public:接口方法默认public,实现类不能用更低权限 - 不能抛出比接口更宽泛的受检异常
- 多个接口有同名的 default 方法时,实现类必须显式重写解决冲突
🔍 面试官五大高频追问(满分标准答案)
追问1:重载和重写底层分别是怎么实现的?JVM怎么知道调用哪个方法?
回答要点:重载是编译期静态绑定,重写是运行期动态绑定。
详细回答:
重载的调用是在编译期决定的。当编译器遇到一个重载方法调用时,会根据方法签名(方法名 + 参数类型列表) 来确定具体调用哪个重载版本,并将该方法引用写入字节码的
invokevirtual指令的操作数中。一旦编译完成,调用目标就固定了——这是静态绑定。重写的调用是在运行期决定的。编译器在编译时并不知道实际对象类型,只确定方法名和参数。运行时,JVM执行
invokevirtual指令时,会通过实际对象的类型找到其虚方法表(vtable),然后根据方法签名在表中查找对应的方法地址。这是动态绑定。
追问2:为什么静态方法不能重写?如果子类定义了和父类同名的静态方法呢?
回答要点:静态方法属于类,不属于对象,没有运行时动态分派。
详细回答:
class Parent {
static void show() { System.out.println("Parent static"); }
void print() { System.out.println("Parent instance"); }
}
class Child extends Parent {
static void show() { System.out.println("Child static"); } // 这是隐藏,不是重写
@Override
void print() { System.out.println("Child instance"); } // 这是重写
}
Parent p = new Child();
p.show(); // 输出"Parent static" —— 静态方法属于类,只看引用类型
p.print(); // 输出"Child instance" —— 实例方法重写,看实际对象类型
静态方法的调用是在编译期决定的,基于引用类型而非实际对象类型。子类定义同名静态方法时,叫做方法隐藏(Method Hiding),不是重写。加上
@Override也会报编译错误。
追问3:重载能仅靠返回类型不同来区分吗?
答:
不能。因为Java编译器在方法调用时,是根据方法名 + 参数列表来确定调用哪个方法的,返回值类型并不参与决策。如果仅返回类型不同,编译器无法区分调用的是哪个版本,会报错:
class Calc {
int add(int a, int b) { return a + b; }
double add(int a, int b) { return a + b; } // ❌ 编译错误!方法已存在
}
追问4:重写时,子类方法的返回类型必须和父类完全相同吗?
答:
不必须。Java支持协变返回类型(Covariant Return Type)——从Java 5开始,重写方法的返回类型可以是父类方法返回类型的子类。这在JDK源码中很常见:
class Parent {
Object get() { return new Object(); }
}
class Child extends Parent {
@Override
String get() { return "hello"; } // ✅ 合法!String是Object的子类
}
优势:协变返回类型让子类方法的返回类型更精确,调用方无需强制类型转换,API更易用。
追问5:重载和重写,哪个更符合多态的定义?
回答要点:重写是真正的多态(运行时多态),重载不是多态。
详细回答:
严格来说,**多态(Polymorphism)**指的是“同一个行为,在不同对象上有不同的表现”,即运行时根据实际对象类型来决定调用哪个方法。**重写(Override)**才是真正的多态。
重载是编译时就确定调用哪个方法,不涉及运行时动态决策,所以重载不算多态。很多人把重载归为多态的一部分(“编译时多态”),但这是一种不严谨的说法。在Java规范中,多态通常指的就是运行时多态。
总结:重载是编译器在编译时决定,重写是JVM在运行时决定。只有重写是多态。
💣 常见坑点
坑1:静态方法隐藏,被误认为重写
class Parent {
static void show() { System.out.println("Parent"); }
}
class Child extends Parent {
static void show() { System.out.println("Child"); } // 隐藏,不是重写
}
Parent p = new Child();
p.show(); // 输出"Parent" —— 基于引用类型,不是实际对象类型
坑2:重写时访问修饰符更严格
class Parent {
protected void method() {}
}
class Child extends Parent {
@Override
private void method() {} // ❌ 编译错误!访问修饰符更严格
}
坑3:重写时抛出更宽泛的异常
class Parent {
void method() throws IOException {}
}
class Child extends Parent {
@Override
void method() throws Exception {} // ❌ 编译错误!Exception比IOException更宽泛
}
坑4:仅靠返回类型不同不能重载
class Calc {
int add(int a, int b) { return a + b; }
double add(int a, int b) { return a + b; } // ❌ 编译错误!
}
坑5:泛型擦除导致的重载冲突(附可运行报错Demo)
import java.util.*;
class OverloadConflictDemo {
// 下面两个方法编译后参数类型都是 List,无法区分
void print(List<String> list) {} // ❌ 报错:与下一行冲突
void print(List<Integer> list) {} // ❌ 报错:与上一行冲突
}
由于泛型擦除,两个方法的参数在编译后都是
List,无法形成重载。
💻 可运行验证代码
import java.util.*;
public class OverloadOverrideDemo {
// ===== 重载演示 =====
static class Calculator {
public int add(int a, int b) {
System.out.println("调用: int add(int, int)");
return a + b;
}
public int add(int a, int b, int c) {
System.out.println("调用: int add(int, int, int)");
return a + b + c;
}
public double add(double a, double b) {
System.out.println("调用: double add(double, double)");
return a + b;
}
}
// ===== 重写演示(继承场景) =====
static class Parent {
public String getMessage() {
return "Parent 的消息";
}
public Object getObject() {
return new Object();
}
}
static class Child extends Parent {
@Override
public String getMessage() {
return "Child 的消息"; // 重写父类方法
}
@Override
public String getObject() { // 协变返回类型:String是Object的子类
return "Child 的字符串对象";
}
}
// ===== 重写演示(接口场景) =====
interface Flyable {
void fly(); // 抽象方法
default void glide() {
System.out.println("默认滑翔");
}
}
static class Bird implements Flyable {
@Override
public void fly() {
System.out.println("鸟儿飞行");
}
@Override
public void glide() {
System.out.println("鸟儿滑翔");
}
}
// ===== 静态方法隐藏演示 =====
static class StaticParent {
static void show() { System.out.println("StaticParent.show()"); }
}
static class StaticChild extends StaticParent {
static void show() { System.out.println("StaticChild.show()"); } // 隐藏,不是重写
}
public static void main(String[] args) {
// 1. 重载验证:编译期决定
Calculator calc = new Calculator();
calc.add(1, 2); // 调用 int add(int, int)
calc.add(1, 2, 3); // 调用 int add(int, int, int)
calc.add(1.0, 2.0); // 调用 double add(double, double)
// 2. 重写验证(继承):运行时决定
Parent p = new Child();
System.out.println(p.getMessage()); // 输出"Child 的消息"(运行时多态)
System.out.println(p.getObject()); // 输出"Child 的字符串对象"(协变返回类型验证)
// 3. 重写验证(接口)
Bird bird = new Bird();
bird.fly(); // 输出"鸟儿飞行"
bird.glide(); // 输出"鸟儿滑翔"
// 4. 静态方法隐藏验证:编译期决定
StaticParent sp = new StaticChild();
sp.show(); // 输出"StaticParent.show()"(基于引用类型,不是实际对象类型)
}
}
预期输出:
调用: int add(int, int)
调用: int add(int, int, int)
调用: double add(double, double)
Child 的消息
Child 的字符串对象
鸟儿飞行
鸟儿滑翔
StaticParent.show()
编译验证命令(展示重载编译期确定):
javac OverloadOverrideDemo.java
javap -c OverloadOverrideDemo\$Calculator.class # 查看字节码,确认调用哪个重载版本
❓ 评论区挑战
挑战题1
问题:下面代码的输出是什么?为什么?
class Parent {
void print(Object o) { System.out.println("Parent: Object"); }
}
class Child extends Parent {
void print(String s) { System.out.println("Child: String"); }
}
public class Test {
public static void main(String[] args) {
Parent p = new Child();
p.print("hello");
}
}
A. 输出 “Parent: Object”
B. 输出 “Child: String”
C. 编译报错,方法调用不明确
D. 运行时异常
挑战题2
问题:如果
Child类同时重写了父类的print(Object o)方法,代码会怎样?
class Parent {
void print(Object o) { System.out.println("Parent: Object"); }
}
class Child extends Parent {
@Override
void print(Object o) { System.out.println("Child: Object"); } // 重写
void print(String s) { System.out.println("Child: String"); } // 重载
}
public class Test {
public static void main(String[] args) {
Parent p = new Child();
p.print("hello");
}
}
A. 输出 “Parent: Object”
B. 输出 “Child: Object”
C. 输出 “Child: String”
D. 编译报错
💬 欢迎在评论区写出你的答案和理由。我会在下一篇文章中发布后更新该文章,公布答案及错误选项逐项解析。
📌 总结
| 维度 | 重载(Overload) | 重写(Override) |
|---|---|---|
| 发生位置 | 同一个类中 | 子类与父类/接口之间 |
| 核心目的 | 提供多种参数版本 | 子类定制父类行为 |
| 绑定时机 | 编译期(静态绑定) | 运行期(动态绑定) |
| 方法名 | 相同 | 相同 |
| 参数列表 | 必须不同 | 必须相同 |
| 返回类型 | 可以不同 | 必须相同(或协变返回类型) |
| 访问修饰符 | 可以不同 | 不能比父类更严格 |
| 异常 | 可以不同 | 不能抛出父类没有的受检异常 |
| 静态方法 | 可以重载 | ❌ 不能重写(只能隐藏) |
| 是否是多态 | ❌ 不是 | ✅ 是(运行时多态) |
面试官最看重的三个点:
- 绑定时机:重载编译期(静态绑定),重写运行期(动态绑定)——这是区分度的关键
- 静态方法问题:静态方法不能重写,只能隐藏
- 参数列表的严格性:重载必须改参数,重写参数必须完全一致
📚 系列导航
- 上一篇:面试官问:接口和抽象类有什么区别?
- 下一篇预告:面试官问:Java异常体系是怎么设计的?
- 全部85题目录:点击查看
💬 你在实际开发中遇到过因为静态方法隐藏导致的诡异Bug吗?或者被面试官用“重载vs重写”的陷阱题坑过?欢迎评论区分享你的故事。
&spm=1001.2101.3001.5002&articleId=162256609&d=1&t=3&u=de2b2a1c6a8640cf89fdf1be2b1b109c)
2907

被折叠的 条评论
为什么被折叠?



