首页 / Java 入门教程 / super 关键字

Java 入门教程

super 关键字

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

JavaJava 入门教程super构造方法继承初始化顺序

本节目标:掌握 super 的三种用法,并搞清楚 new 一个子类对象时,父类和子类的代码到底按什么顺序执行。

super 指的是什么

this 指当前对象自己,super 指当前对象「作为父类的那一部分」。

注意措辞:super 不是另一个对象。一个 Student 实例只有一个对象,super 只是让你换个视角去访问同一个对象里属于父类的成员。

super 一共三种用法:

  1. super(...) —— 调用父类构造方法
  2. super.字段名 —— 访问父类字段
  3. super.方法名() —— 调用父类方法

下面逐个拆开。

用法一:super(…) 调用父类构造方法

这是最常用、也最容易出错的一种。

上一章说过,任何构造方法的第一行都必须调用父类构造方法。不写的话,编译器自动补 super();

class Person {
    protected String name;
    protected int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
        System.out.println("Person 构造方法执行");
    }
}

class Student extends Person {
    protected int score;

    public Student(String name, int age, int score) {
        super(name, age);          // 必须是第一行
        this.score = score;
        System.out.println("Student 构造方法执行");
    }
}

new Student("小明", 12, 89) 的输出顺序是:

Person 构造方法执行
Student 构造方法执行

先父后子。因为子类的字段初始化可能依赖父类的状态,父类不先准备好,子类就是在沙子上盖楼。

两条硬性规定

规定一:super(...) 必须是构造方法的第一条语句。

public Student(String name, int age, int score) {
    this.score = score;
    super(name, age);   // 编译错误:call to super must be first statement
}

规定二:super(...)this(...) 不能同时出现。

this(...) 是调用本类的另一个构造方法,它也要求在第一行。两个都想在第一行,只能二选一:

class Student extends Person {
    protected int score;

    public Student(String name, int age, int score) {
        super(name, age);
        this.score = score;
    }

    // 简化版:委托给上面那个构造方法
    public Student(String name) {
        this(name, 0, 0);   // 调本类构造方法,它内部会调 super
    }
}

链条最终一定会走到某个 super(...),父类构造方法一次都不会漏。

Warning

父类如果没有无参构造方法,子类的每个构造方法都必须显式写 super(...)。这是初学者最常见的编译错误之一,报错信息通常是 constructor Xxx in class Xxx cannot be applied to given types

用法二:super.字段 访问父类字段

正常情况下,子类访问父类字段直接写字段名就行,编译器会自动往上找:

class Student extends Person {
    public String hello() {
        return "Hello, " + name;         // 直接写
        // return "Hello, " + this.name; // 等价
        // return "Hello, " + super.name;// 也等价
    }
}

三种写法效果一样。那 super.name 什么时候才是必需的?

答案是:父子类有同名字段时

class Person {
    protected String name = "父类的名字";
}

class Student extends Person {
    protected String name = "子类的名字";   // 遮蔽了父类的 name

    public void print() {
        System.out.println(name);        // 子类的名字
        System.out.println(this.name);   // 子类的名字
        System.out.println(super.name);  // 父类的名字
    }
}

这种现象叫字段遮蔽(field hiding)。父类那个 name 并没有消失,仍然占着内存,只是被子类的同名字段挡住了,必须用 super. 才能摸到。

Warning

字段遮蔽在实际开发中几乎全是 bug 来源,不要主动使用。第 39 章说的「严禁定义与父类重名的字段」就是这个意思。这里讲它,是为了让你在读别人代码时看得懂。

用法三:super.方法() 调用父类方法

上一章已经见过,这里补充两个要点。

要点一:这是「扩展」而非「替换」的标准写法。

class Person {
    public String describe() {
        return "姓名=" + name + ", 年龄=" + age;
    }
}

class Student extends Person {
    @Override
    public String describe() {
        return super.describe() + ", 分数=" + score;   // 父类的信息 + 自己的
    }
}

重写 toString() 时这招特别顺手,不用把父类字段再拼一遍。

要点二:super.方法() 是唯一能「穿透多态」的写法。

普通调用 this.describe() 会走多态,执行最下层子类的版本。只有 super.describe() 明确锁定父类那一份,不受多态影响。

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

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

    public void test() {
        run();         // Student.run —— 走多态
        this.run();    // Student.run —— 同上
        super.run();   // Person.run  —— 锁定父类
    }
}
Note

super 只能往上跳一层。没有 super.super.run() 这种写法,Java 不支持越级调用祖父类的方法。

对象初始化的完整顺序

super(...)、字段初始化、代码块放一起,new 一个子类对象时的执行顺序是这样的:

  1. 父类的静态字段和静态代码块(只在类首次加载时执行一次)
  2. 子类的静态字段和静态代码块(同上,只执行一次)
  3. 父类的实例字段初始化和实例代码块
  4. 父类的构造方法体
  5. 子类的实例字段初始化和实例代码块
  6. 子类的构造方法体

用代码验证:

public class Main {
    public static void main(String[] args) {
        System.out.println("--- 第一次 new ---");
        new Student();
        System.out.println("--- 第二次 new ---");
        new Student();
    }
}

class Person {
    static { System.out.println("1. Person 静态代码块"); }
    { System.out.println("3. Person 实例代码块"); }

    public Person() { System.out.println("4. Person 构造方法"); }
}

class Student extends Person {
    static { System.out.println("2. Student 静态代码块"); }
    { System.out.println("5. Student 实例代码块"); }

    public Student() { System.out.println("6. Student 构造方法"); }
}

运行:

javac Main.java
java Main

输出:

--- 第一次 new ---
1. Person 静态代码块
2. Student 静态代码块
3. Person 实例代码块
4. Person 构造方法
5. Student 实例代码块
6. Student 构造方法
--- 第二次 new ---
3. Person 实例代码块
4. Person 构造方法
5. Student 实例代码块
6. Student 构造方法

第二次 new 时静态块不再执行——它们只在类加载时跑一次。

构造方法里调用可重写方法:一个经典陷阱

这个坑值得单独拎出来。

class Person {
    public Person() {
        init();          // 危险:调用了可被重写的方法
    }

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

class Student extends Person {
    private String name = "小明";

    @Override
    public void init() {
        System.out.println("Student.init, name = " + name);
    }
}

new Student() 输出的是:

Student.init, name = null

name 明明赋了值,怎么是 null

回顾初始化顺序:父类构造方法(第 4 步)比子类字段初始化(第 5 步)更早。父类构造方法里调 init(),多态让它跳到 Student.init(),而这时 name 还没来得及赋值,只有默认值 null

Warning

不要在构造方法里调用可被重写的方法。要么把方法声明为 final/private,要么把初始化逻辑挪到构造方法之外。这是《Effective Java》明确点名的反模式,也是面试常考点。

super 与 this 对照

对比项thissuper
指代当前对象自身当前对象的父类部分
调构造方法this(...) 调本类其他构造方法super(...) 调父类构造方法
访问字段this.x 优先找本类super.x 直接找父类
调方法this.m() 走多态super.m() 锁定父类实现
静态方法中能否用不能不能

两者都不能出现在 static 方法里,因为静态方法属于类,压根没有「当前对象」。

小结

  • super 代表当前对象的父类部分,不是另一个对象。
  • super(...) 调父类构造方法,必须在第一行,且不能与 this(...) 共存。
  • super.字段 用于突破字段遮蔽;日常不要制造同名字段。
  • super.方法() 用于在重写中复用父类实现,且不受多态影响,只能上跳一层。
  • 初始化顺序:父静态 → 子静态 → 父实例块 → 父构造 → 子实例块 → 子构造。
  • 别在构造方法里调用可重写方法,否则会读到未初始化的子类字段。

下一章讲 final,看看怎么主动关上继承和重写这两扇门。