首页 / Java 入门教程 / 方法重写(Override)

Java 入门教程

方法重写(Override)

本教程共 100 篇 · 第 40 篇 · 更新于 2026-08-05 · 约 6 分钟阅读

JavaJava 入门教程方法重写Override重载面向对象

40. 方法重写(Override)

本节目标:搞懂子类怎么改写父类的方法,掌握重写的硬性规则,并且能一眼分清「重写」和「重载」。

子类不满意父类的实现怎么办

继承让子类白捡父类的方法,但捡来的东西不一定合用。

Person 有个 run() 方法,打印「人在跑」。到了 Student 这里,我们想让它打印「学生在跑」。总不能新起个名字叫 runAsStudent() 吧?

Java 的做法是重写(Override):在子类里写一个和父类签名完全相同的方法,把父类的实现盖掉。

class Person {
    public void run() {
        System.out.println("Person.run");
    }
}

class Student extends Person {
    @Override
    public void run() {
        System.out.println("Student.run");   // 盖掉父类的实现
    }
}
```bash

调用 `new Student().run()`,输出的是 `Student.run`。父类那份实现被彻底替换了。

> [!NOTE]
> 「重写」和「覆写」「覆盖」说的是同一件事,英文都是 Override。本教程统一用「重写」。

## 什么算「签名相同」

方法签名 = 方法名 + 参数列表(参数类型和顺序)。注意,**返回值不算签名的一部分**

```java
class Person {
    public void run() {}
}

class Student extends Person {
    public void run(String s) {}   // 参数不同 → 不是重写,是重载
    // public int run() {}         // 只有返回值不同 → 编译错误!
}

第二行为什么直接报错?因为 Java 不允许在同一个类型体系里出现「名字和参数都一样、只有返回值不同」的两个方法——调用时无法区分。

重写的五条规则

规则不多,但每条都会在编译期咬人。

1. 方法名和参数列表必须完全一致。 差一个参数就变成重载。

2. 返回值类型必须相同,或者是父类返回值的子类。

后半句叫「协变返回类型」(Java 5 引入):

class Person {
    public Person copy() { return new Person(); }
}

class Student extends Person {
    @Override
    public Student copy() { return new Student(); }  // 返回 Student,是 Person 的子类,合法
}
```java

**3. 访问权限不能比父类更严格。**

```java
class Person {
    public void run() {}
}

class Student extends Person {
    // @Override
    // protected void run() {}   // 编译错误:不能把 public 缩小为 protected
}

放开注释再编译,报错原文是这样:

error: run() in Student cannot override run() in Person
    protected void run() {}
                   ^
  attempting to assign weaker access privileges; was public
1 error
```java

`attempting to assign weaker access privileges` 这句话就是「你想把权限改小」,见到它去检查修饰符即可。

道理很直白:外界拿着 `Person` 类型的变量在调 `run()`,如果实际对象是 `Student` 而 `run()` 变成了 `protected`,调用就崩了。所以只能放宽,不能收紧。放宽的方向是 `private → 默认 → protected → public`。

| 父类方法权限 | 子类重写可用的权限 |
|-------------|------------------|
| `public` | 只能 `public` |
| `protected` | `protected`、`public` |
| 默认(包私有) | 默认、`protected`、`public` |
| `private` | 子类看不见,谈不上重写 |

**4. 抛出的受检异常不能比父类更宽。**

```java
class Person {
    public void run() throws java.io.IOException {}
}

class Student extends Person {
    @Override
    public void run() throws java.io.FileNotFoundException {}  // 更具体的子类异常,合法
}

不能改成 throws Exception,那是扩大了调用方需要处理的范围。也可以完全不抛,那是收窄,永远合法。

5. staticfinalprivate 方法不能被重写。

  • final 方法:父类明确禁止重写(第 42 章细讲)。
  • private 方法:子类根本看不见它,在子类写同名方法只是「新方法」,不是重写。
  • static 方法:属于类不属于对象,子类写同名静态方法叫「隐藏」(hiding),行为和重写完全不同——它按变量的声明类型走,不看实际对象。

@Override 注解值得每次都写

@Override 不是必须的,去掉照样能跑。但它能救命。

假设你想重写 run(),手抖写成了 runn()

class Student extends Person {
    public void runn() {          // 拼错了,编译器不吭声
        System.out.println("Student.run");
    }
}
```java

这段代码编译通过、运行正常,只是你的重写压根没生效。调 `run()` 时执行的还是父类版本,排查半天找不到原因。

加上 `@Override` 就不一样了:

```java
class Student extends Person {
    @Override                     // 编译错误:父类没有 runn() 方法
    public void runn() { }
}

编译器会检查「你标注要重写的方法,父类真的有吗」。有错立刻报出来。

报错原文:

error: method does not override or implement a method from a supertype
    @Override
    ^
1 error
```bash

这句话直译就是「这个方法既没重写父类的,也没实现接口的」。九成情况是方法名拼错,或者参数类型对不上(比如父类是 `List` 你写成了 `ArrayList`)。

`@Override` 的第二个价值在改动时。哪天父类把 `run()` 改名成 `move()`,所有标了 `@Override` 的子类会立刻编译报错,提醒你一起改;没标注的则会悄悄退化成一个没人调用的新方法。

> [!TIP]
> 养成习惯:只要是重写,就加 `@Override`。这是零成本的保险。`@Override` 从 Java 5 引入,Java 6 起还支持标注对接口方法的实现。

## 调用父类的原实现:super

重写不一定要完全推翻父类。经常是「在父类基础上加点东西」。

用 `super.方法名()` 可以调到父类被盖掉的那份实现:

```java
class Person {
    protected String name;

    public Person(String name) { this.name = name; }

    public String hello() {
        return "Hello, " + name;
    }
}

class Student extends Person {
    public Student(String name) { super(name); }

    @Override
    public String hello() {
        return super.hello() + "! 我是学生";   // 复用父类逻辑,再追加
    }
}

new Student("小明").hello() 输出 Hello, 小明! 我是学生

关于 super 的完整用法,下一章会专门讲。

重写 vs 重载:一张表说清

这是面试高频题,也是初学者最容易混的两个词。

对比项重写 Override重载 Overload
发生位置子类与父类之间同一个类内部(也可跨父子类)
方法名必须相同必须相同
参数列表必须相同必须不同
返回值相同或是子类型无限制
访问权限不能缩小无限制
绑定时机运行期(动态)编译期(静态)

一句话记忆:重载是「同名不同参」,重写是「同名同参换实现」

看个混合示例:

class Calculator {
    public int add(int a, int b) { return a + b; }
    public double add(double a, double b) { return a + b; }   // 重载
}

class SmartCalculator extends Calculator {
    @Override
    public int add(int a, int b) {                            // 重写
        System.out.println("智能计算中");
        return a + b;
    }
}
```bash

`add(int, int)` 和 `add(double, double)` 是重载关系。`SmartCalculator.add(int, int)` 和 `Calculator.add(int, int)` 是重写关系。

## 完整可运行示例

```java
public class Main {
    public static void main(String[] args) {
        Person p = new Person("小红");
        Student s = new Student("小明");

        System.out.println(p.hello());   // Hello, 小红
        System.out.println(s.hello());   // Hello, 小明! 我是学生

        p.run();   // Person.run
        s.run();   // Student.run

        // 关键:变量声明为父类,实际对象是子类
        Person ps = new Student("小刚");
        ps.run();  // Student.run —— 执行的是子类版本
    }
}

class Person {
    protected String name;

    public Person(String name) { this.name = name; }

    public void run() {
        System.out.println("Person.run");
    }

    public String hello() {
        return "Hello, " + name;
    }
}

class Student extends Person {
    public Student(String name) { super(name); }

    @Override
    public void run() {
        System.out.println("Student.run");
    }

    @Override
    public String hello() {
        return super.hello() + "! 我是学生";
    }
}
javac Main.java
java Main
```bash

最后三行是重点:`ps` 声明成 `Person`,但实际对象是 `Student`,调用 `run()` 执行的是 `Student` 那份。这个「看实际对象、不看声明类型」的特性叫**多态**,第 43 章专讲。

## 小结

- 重写 = 子类写一个签名和父类完全相同的方法,把实现换掉。
- 五条规则:签名一致、返回值相同或协变、权限不缩小、受检异常不扩大、`static`/`final`/`private` 不可重写。
- `@Override` 让编译器帮你检查重写是否写对,建议每次都加。
- `super.方法()` 可以调用父类被盖掉的实现,用于「在父类基础上扩展」。
- 重载看参数,重写看实现;重载编译期定,重写运行期定。

## 常见疑问

**Q:协变返回类型有什么实际用处?**

链式调用时特别舒服。父类 `copy()` 返回 `Person`,子类重写后返回 `Student`,那么 `new Student("小明").copy().study()` 能直接写;如果返回值卡死成 `Person`,就得先强制转换成 `Student` 才能调子类特有的方法。少一次转换,少一处 `ClassCastException` 的风险。

**Q:子类能不能重写父类的构造方法?**

不能。构造方法名必须和类名一致,子类和父类类名不同,天然没法「签名相同」。子类只能用 `super(...)` 调父类构造,这是下一章的内容。

**Q:`static` 方法写同名的,到底算什么?**

算「隐藏」,不算重写。区别在调用规则:重写看**实际对象**,隐藏看**声明类型**。

```java
Person p = new Student("小明");
p.run();          // 重写 Student.run(看实际对象)
Person.staticM(); // 隐藏 Person.staticM(看声明的类)

所以静态方法建议一律用 类名.方法名() 调用,别用对象引用去调,免得被这条规则绕晕。

下一章把 super 的三种用法一次讲透。