核心概念总结(一句话区分)
- 方法重载:一个类中,方法名相同,参数列表不同(参数个数、类型或顺序不同),编译时多态,与返回值类型无关。
- 方法重写:子类中,方法名、参数列表、返回值类型(或其子类)必须与父类完全相同(除了
private和final方法),运行时多态,是对父类方法功能的“重新实现”。
详细对比表格
| 特性 | 方法重载 | 方法重写 |
|---|---|---|
| 定义位置 | 同一个类中,或者在父子类中。 | 必须有继承关系的类中,子类重写父类的方法。 |
| 方法名 | 必须相同。 | 必须相同。 |
| 参数列表 | 必须不同。 (参数个数、类型、顺序至少有一项不同) |
必须完全相同。 (参数个数、类型、顺序都必须一样) |
| 返回值类型 | 可以相同,也可以不同。 (如果不同,必须是不同参数类型的返回值,否则会编译报错) |
必须相同或是其子类。 (Java 5+ 协变返回类型) |
| 访问修饰符 | 没有限制。 | 子类方法的访问权限不能比父类更严格。 (父类是 public,子类不能是 protected) |
| 异常抛出 | 没有限制。 | 子类方法抛出的异常不能比父类更宽泛(不能是父类异常的父类)。 |
@Override 注解 |
不使用。 | 强烈建议使用,用于编译器检查,确保正确重写。 |
| 触发时机 | 编译时决定调用哪个方法。 | 运行时决定调用哪个方法(通过对象的实际类型)。 |
| 核心思想 | “一个类,多种功能” 提供同一功能的多种版本,根据传入参数不同选择执行。 |
“子类特化” 子类根据自身需求,重新实现从父类继承来的方法。 |
| 英文 | Overloading | Overriding |
详细解释与代码示例
方法重载
核心规则:方法名相同,参数列表不同。

重载让一个类可以根据传入参数的不同,提供多种“版本”的功能,使代码更直观、易用。
示例:一个 Calculator 类
public class Calculator {
// 重载方法 1: 两个整数相加
public int add(int a, int b) {
System.out.println("执行 int add(int, int)");
return a + b;
}
// 重载方法 2: 三个整数相加 (参数个数不同)
public int add(int a, int b, int c) {
System.out.println("执行 int add(int, int, int)");
return a + b + c;
}
// 重载方法 3: 两个 double 相加 (参数类型不同)
public double add(double a, double b) {
System.out.println("执行 double add(double, double)");
return a + b;
}
// 错误示例:这不是重载,编译器会报错 "duplicate method"
// 因为参数列表完全相同 (int, int)
// public long add(int x, int y) { ... }
}
调用方式:
public class Main {
public static void main(String[] args) {
Calculator calc = new Calculator();
int sum1 = calc.add(10, 20); // 调用 add(int, int)
int sum2 = calc.add(10, 20, 30); // 调用 add(int, int, int)
double sum3 = calc.add(10.5, 20.5); // 调用 add(double, double)
System.out.println("sum1: " + sum1);
System.out.println("sum2: " + sum2);
System.out.println("sum3: " + sum3);
}
}
编译时多态:在编译阶段,Java 编译器根据你传入的参数类型和数量,就能确定要调用哪个 add 方法,这个过程在编译时就完成了。

方法重写
核心规则:在继承关系中,子类重新实现一个与父类方法签名完全相同(除了访问权限和异常)的方法。
重写是实现多态的关键,它允许子类对象在调用父类方法时,表现出子类特有的行为。
示例:Animal 和 Dog 类
父类 Animal.java

public class Animal {
// 父类的方法
public void makeSound() {
System.out.println("动物发出声音");
}
}
子类 Dog.java
public class Dog extends Animal {
// 使用 @Override 注解,让编译器检查我们是否正确地重写了方法
@Override
public void makeSound() {
// 子类提供了自己的实现
System.out.println("汪汪汪!");
}
}
调用方式:
public class Main {
public static void main(String[] args) {
Animal myAnimal = new Animal();
Animal myDog = new Dog(); // 向上转型:父类引用指向子类对象
myAnimal.makeSound(); // 输出: 动物发出声音
myDog.makeSound(); // 输出: 汪汪汪!
}
}
运行时多态:关键在于 myDog.makeSound() 这一行。
myDog的编译时类型是Animal,所以编译器检查makeSound()方法是否存在,它确实存在。- 但
myDog的运行时类型(或称实际类型)是Dog。 - 在程序运行时,JVM 会根据对象的实际类型(
Dog)去查找并执行Dog类中的makeSound()方法,而不是Animal类中的。
这就是为什么 myDog.makeSound() 输出的是 "汪汪汪!"。
关键区别与常见误区
参数列表 vs 方法签名
- 重载关心的是参数列表不同。
- 重写要求方法签名(方法名 + 参数列表)完全相同。
返回值类型
-
重载:返回值类型可以不同,但前提是参数列表必须不同,如果两个方法只有返回值类型不同,而参数列表完全相同,则不是重载,是编译错误。
-
重写:在 Java 5 之前,返回值类型必须完全相同,从 Java 5 开始,引入了协变返回类型,允许子类方法的返回值类型是父类方法返回值类型的子类。
示例:
class Parent { public Parent getObject() { return new Parent(); } } class Child extends Parent { @Override public Child getObject() { // 返回 Child 是 Parent 的子类,所以可以重写 return new Child(); } }
private 和 final 方法
private方法:不能被重写,因为它们对子类是不可见的,子类中如果定义一个与父类private方法签名相同的方法,那只是定义了一个新的、无关的方法,不是重写。final方法:不能被重写,这保证了父类的核心逻辑不会被任何子类随意修改。
访问权限
-
重写时,子类方法的访问权限不能比父类更严格。
- 父类
public-> 子类必须是public - 父类
protected-> 子类可以是protected或public - 父类默认(包私有) -> 子类可以是默认、
protected或public - 父类
private-> 不能重写
这是为了保证“里氏替换原则”(LSP):子类对象应该能够替换任何父类对象,并且程序行为不变,如果子类方法的访问权限更严格,那么一个父类类型的引用可能就无法调用子类中那个“更私有的”方法了。
- 父类
| 重载 | 重写 | |
|---|---|---|
| 中文 | 同一个类,方法名相同,参数不同 | 子类继承父类,方法名和参数都相同,功能重新实现 |
| 英文 | Overloading | Overriding |
| 目的 | 方便调用,提供多种功能实现 | 实现多态,允许子类定制行为 |
| 关系 | 同一个类或父子类 | 必须有继承关系 |
| 决定时机 | 编译时 (静态绑定) | 运行时 (动态绑定) |
| 记住 | 参数不同 | 签名相同,子类特化 |
记住这个简单的类比:
- 重载就像一个工具箱里的多个不同功能的螺丝刀(十字、一字、内六角),它们都叫“螺丝刀”,但功能不同,根据你的需求(参数)选择哪个。
- 重写就像儿子继承父亲的手艺,父亲会“做菜”,儿子也会“做菜”,但儿子会做一道“宫保鸡丁”,这是他自己独特的做法(实现),而不是简单地沿用父亲的做法。
