重写与重载
核心概念
重写(Override)和重载(Overload)是Java中多态性的两种重要表现形式:
- 重写(Override):运行时多态,发生在继承关系中
- 重载(Overload):编译时多态,发生在同一个类中
理解它们的机制和区别,是掌握Java面向对象编程的关键。
一、方法重写(Override)
1.1 基本语法
class Parent {
public void method(int x) {
System.out.println("Parent method");
}
}
class Child extends Parent {
// 重写父类方法
@Override
public void method(int x) {
System.out.println("Child method");
}
}
// 使用
Parent p = new Child();
p.method(10); // 输出:Child method(运行时多态)
1.2 重写的规则(两同两小一大)
| 规则 | 说明 | 示例 |
|---|---|---|
| 方法名相同 | 必须完全一致 | method() |
| 参数列表相同 | 参数类型和顺序 | method(int x, String y) |
| 返回类型相同或更小(协变返回) | 可以是原返回类型的子类 | Object → String |
| 异常相同或更小 | 不能抛出更大的异常 | IOException → FileNotFoundException |
| 访问权限相同或更大 | 不能降低可见性 | protected → public |
1.3 @Override注解
class Parent {
public void method() {}
}
class Child extends Parent {
// ✅ 正确:使用@Override检查重写
@Override
public void method() {}
// ❌ 错误:方法名写错,编译器会报错
@Override
public void methd() {} // 编译错误
}
1.4 协变返回类型
class Animal {
public Animal getInstance() {
return new Animal();
}
}
class Dog extends Animal {
// 协变返回:返回类型可以是原返回类型的子类
@Override
public Dog getInstance() {
return new Dog();
}
}
1.5 异常处理
class Parent {
public void method() throws IOException {
// 抛出较大的异常
}
}
class Child extends Parent {
// ✅ 正确:抛出更小的异常
@Override
public void method() throws FileNotFoundException {
// 抛出更具体的异常
}
// ❌ 错误:不能抛出更大的异常
// @Override
// public void method() throws Exception { } // 编译错误
}
二、方法重载(Overload)
2.1 基本语法
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;
}
}
// 使用
Calculator calc = new Calculator();
calc.add(1, 2); // 调用 int add(int, int)
calc.add(1.0, 2.0); // 调用 double add(double, double)
calc.add(1, 2, 3); // 调用 int add(int, int, int)
2.2 重载的规则
| 规则 | 说明 | 示例 |
|---|---|---|
| 方法名相同 | 必须完全一致 | add() |
| 参数列表不同 | 参数类型、数量、顺序 | add(int,int) vs add(double,double) |
| 返回类型无关 | 仅靠返回类型区分无效 | int add() vs double add() 是错误的 |
2.3 重载的类型匹配
class OverloadDemo {
public void method(int x) {
System.out.println("int");
}
public void method(double x) {
System.out.println("double");
}
public void method(Integer x) {
System.out.println("Integer");
}
}
// 调用顺序
OverloadDemo demo = new OverloadDemo();
demo.method(10); // 输出:int(精确匹配)
demo.method(10.0); // 输出:double
demo.method(Integer.valueOf(10)); // 输出:Integer
2.4 可变参数重载
class VarargsDemo {
public void method(String... args) {
System.out.println("String...");
}
public void method(int... args) {
System.out.println("int...");
}
}
VarargsDemo demo = new VarargsDemo();
demo.method("a", "b"); // 输出:String...
demo.method(1, 2, 3); // 输出:int...
三、重写 vs 重载
3.1 核心区别
| 特性 | 重写(Override) | 重载(Overload) |
|---|---|---|
| 发生位置 | 继承关系中的子类 | 同一个类中 |
| 方法名 | 相同 | 相同 |
| 参数列表 | 相同 | 不同 |
| 返回类型 | 相同或协变 | 无关 |
| 多态类型 | 运行时多态 | 编译时多态 |
| 访问权限 | 不能降低 | 无限制 |
3.2 多态性对比
class Parent {
public void method(int x) {
System.out.println("Parent");
}
}
class Child extends Parent {
// 重写:运行时多态
@Override
public void method(int x) {
System.out.println("Child");
}
// 重载:编译时多态
public void method(String s) {
System.out.println("Child-String");
}
}
Parent p = new Child();
p.method(10); // 输出:Child(重写,运行时)
p.method("hello"); // 编译错误(重载需要具体类型)
3.3 方法调用的绑定
class Parent {
public static void staticMethod() {
System.out.println("Parent static");
}
public void instanceMethod() {
System.out.println("Parent instance");
}
}
class Child extends Parent {
public static void staticMethod() {
System.out.println("Child static");
}
@Override
public void instanceMethod() {
System.out.println("Child instance");
}
}
Parent p = new Child();
p.staticMethod(); // 输出:Parent static(静态绑定)
p.instanceMethod(); // 输出:Child instance(动态绑定)
绑定类型:
- 静态绑定(编译时):static、private、final方法
- 动态绑定(运行时):普通实例方法
四、高频面试题
Q1: 重写和重载有什么区别?
答:
- 重写:继承关系中子类覆盖父类方法,运行时多态
- 重载:同一个类中方法名相同但参数不同,编译时多态
- 重写关注继承,重载关注同一类中的多个方法
Q2: 为什么static方法不能被重写?
答:
- static方法属于类,不属于对象实例
- 静态方法在编译时绑定,不支持运行时多态
- 子类中定义同名static方法是隐藏,不是重写
Q3: 协变返回类型是什么?
答:
- 子类重写方法可以返回父类方法返回类型的子类
- 增加了方法返回值的灵活性
- JDK 5引入的特性
Q4: 重载方法是如何匹配的?
答:
- 精确匹配优先
- 基本类型自动提升(int → long → float → double)
- 装箱/拆箱
- 可变参数最后匹配
Q5: @Override注解的作用?
答:
- 编译器检查是否真的重写了父类方法
- 防止方法名拼写错误
- 提高代码可读性
五、最佳实践
✅ 推荐做法
// 1. 使用@Override注解
@Override
public void method() { }
// 2. 重载方法保持语义一致
public void process(int value) { }
public void process(String value) { }
// 3. 谨慎使用可变参数重载
public void method(String... args) { }
public void method(int... args) { }
// 4. 协变返回类型
@Override
public SubClass getInstance() {
return new SubClass();
}
❌ 避免做法
// 1. 不要重载具有歧义的方法
public void method(int x) { }
public void method(Integer x) { } // 容易混淆
// 2. 避免过度依赖重载
public void process(Object obj) { }
public void process(String str) { } // 可能导致意外调用
// 3. 不要滥用static方法重写
public static void method() { } // 不是真正的重写
相关链接
记忆口诀
重写子类覆盖,运行时多态显
参数类型不变,返回可协变
重载同名方法,参数来区分
静态方法不变,编译时绑定深
@Override护航法,检查重写无烦恼
相关笔记
本篇讲清了"怎么重写/怎么重载";方法重载和方法重写 与 编译时多态和运行时多态 则从"多态"角度再切一刀——重载绑定发生在编译期、重写绑定发生在运行期,这正是运行时多态的实现机制。三篇对照读,"两个绑定时机"这个面试高频点就闭环了。
⬅️ 访问权限 🏠 00-Java ➡️ 面向对象or面向过程
💬 评论