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

Java 入门教程

方法重写(Override)

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

JavaJava 入门教程方法重写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");   // 盖掉父类的实现
    }
}

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

Note

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

什么算「签名相同」

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

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 的子类,合法
}

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

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

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

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

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

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

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");
    }
}

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

加上 @Override 就不一样了:

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

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

报错原文:

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

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

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

Tip

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

调用父类的原实现:super

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

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

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;
    }
}

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

完整可运行示例

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

最后三行是重点: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 方法写同名的,到底算什么?

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

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

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

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