子类构造器与 this(...) / super(...) | JavaSE

子类构造器与 this(...) / super(...)

一、学习目标

学完本章,你应该能够:

  • 能够解释创建子类对象时为什么会先执行父类构造器。
  • 能够解释子类构造器中隐式 super() 的含义。
  • 能够判断父类没有无参数构造器时为什么子类可能编译失败。
  • 能够使用 super(...) 调用父类指定构造器。
  • 能够使用 this(...) 调用当前类中的其他构造器。
  • 能够区分 this(...)super(...)this.xxxsuper.xxx
  • 能够解释 JDK 21 中为什么 this(...)super(...) 必须位于构造器第一条语句。
  • 能够理解构造器链(Constructor Chaining)。
  • 能够设计 People → Teacher 等继承体系中的对象初始化流程。
  • 能够 Debug 常见的父类构造器不存在、构造器递归调用等问题。

二、核心知识

2.1 先回顾:构造器不能被继承

前面已经学习:

public class People {

    public People() {
        System.out.println("People()");
    }
}

子类:

public class Teacher extends People {
}

虽然:

Teacher

继承了 People 的部分成员能力,但是:

构造器不会被子类继承。

所以:

People()

不会自动变成:

Teacher()

这两个构造器分别属于两个不同的类。

但是马上会出现一个问题:

如果构造器不能继承,创建 Teacher 时为什么还会执行 People 的构造器?

这就是本章真正要解决的问题。


2.2 创建子类对象时为什么要初始化父类部分

假设:

public class People {

    private String name;
    private char sex;
}

子类:

public class Teacher extends People {

    private String skill;
}

一个 Teacher 对象从类型模型上不仅拥有:

Teacher 自己定义的部分
skill

还包含继承体系中由 People 定义的状态:

People 部分
name
sex

所以创建:

Teacher teacher = new Teacher();

不能只初始化:

skill

而完全不处理它的父类部分。

可以先建立这样一个概念模型:

Teacher 对象

┌──────────────────────────┐
│ People 定义的对象状态     │
│ name                     │
│ sex                      │
├──────────────────────────┤
│ Teacher 定义的对象状态    │
│ skill                    │
└──────────────────────────┘

因此在执行子类自己的初始化逻辑之前,需要先完成直接父类构造过程。

这就是为什么创建子类对象时会发生:

父类构造过程
    ↓
子类构造过程

2.3 最简单的构造器执行顺序

父类:

public class Fu {

    public Fu() {
        System.out.println("父类构造器执行");
    }
}

子类:

public class Zi extends Fu {

    public Zi() {
        System.out.println("子类构造器执行");
    }
}

测试:

public class Test {

    public static void main(String[] args) {
        Zi zi = new Zi();
    }
}

执行顺序是:

父类构造器执行
子类构造器执行

为什么?

因为子类构造器实际上存在一个重要调用:

public Zi() {
    super();
    System.out.println("子类构造器执行");
}

只不过:

super();

通常被编译器隐式补充,所以我们没有手写出来。


2.4 super() 是什么

在构造器中:

super();

表示:

调用当前类直接父类的无参数构造器。

例如:

class Animal {

    public Animal() {
        System.out.println("Animal()");
    }
}
class Dog extends Animal {

    public Dog() {
        super();
        System.out.println("Dog()");
    }
}

这里:

super();

调用的是:

Animal()

不是任意更上层类的构造器。

也就是说:

Dog
 ↓ super()
Animal

如果 Animal 自己还继承其他类,则由 Animal 的构造器继续完成它自己的父类构造过程。


2.5 默认情况下存在隐式 super()

JDK 21 中,如果某个构造器:

  • 不是 Object 构造器;
  • 开头没有显式调用当前类其他构造器;
  • 也没有显式调用直接父类构造器;

那么编译器会按照语言规则把它理解为首先调用:

super();

例如:

public class Dog extends Animal {

    public Dog() {
        System.out.println("Dog()");
    }
}

可以理解成:

public Dog() {
    super();
    System.out.println("Dog()");
}

因此一定要记住:

“我没有写 super()”不代表父类构造器不会被调用。


2.6 父类必须有能够匹配的无参构造器吗

观察父类:

public class People {

    public People(String name) {
        System.out.println(name);
    }
}

注意:

People

已经显式声明了:

People(String name)

因此编译器不会再为它自动提供默认无参构造器:

People()

此时子类:

public class Teacher extends People {

    public Teacher() {
    }
}

会发生什么?

子类构造器没有显式写:

super(...)

因此按照规则会尝试:

super();

也就是寻找:

People()

但父类并不存在这个构造器。

于是:

编译失败。


2.7 父类没有无参构造器怎么办

假设:

public class People {

    private String name;

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

子类必须选择一个实际存在的父类构造器:

public class Teacher extends People {

    private String skill;

    public Teacher(String name, String skill) {
        super(name);
        this.skill = skill;
    }
}

其中:

super(name);

会调用:

People(String name)

于是:

new Teacher("张老师", "Java")
            │
            ↓
Teacher(String,String)
            │
            ├── super(name)
            │       ↓
            │   People(String)
            │       ↓
            │   初始化 name
            │
            └── 初始化 skill

这就是 super(...) 非常典型的实际应用。


2.8 super(...) 的核心作用

基本语法:

super(参数列表);

作用:

在子类构造器中调用直接父类中参数匹配的构造器。

例如父类:

public People(String name, char sex) {
    this.name = name;
    this.sex = sex;
}

子类:

public Teacher(String name, char sex, String skill) {
    super(name, sex);
    this.skill = skill;
}

这实际上形成了非常清晰的职责划分:

People 构造器
负责初始化 People 自己定义的状态

Teacher 构造器
负责初始化 Teacher 自己增加的状态

而不是让子类绕开父类封装,直接操作父类内部字段。


2.9 完整 People → Teacher 案例

父类:

public class People {

    private String name;
    private char sex;

    public People() {
    }

    public People(String name, char sex) {
        this.name = name;
        this.sex = sex;
    }

    public String getName() {
        return name;
    }

    public char getSex() {
        return sex;
    }
}

子类:

public class Teacher extends People {

    private String skill;

    public Teacher() {
    }

    public Teacher(String name, char sex, String skill) {
        super(name, sex);
        this.skill = skill;
    }

    public String getSkill() {
        return skill;
    }
}

测试:

public class Test {

    public static void main(String[] args) {

        Teacher teacher =
                new Teacher(
                        "张老师",
                        '男',
                        "Java"
                );

        System.out.println(teacher.getName());
        System.out.println(teacher.getSex());
        System.out.println(teacher.getSkill());
    }
}

这里:

super(name, sex);

负责把:

name
sex

交给:

People

自己的构造器初始化。

随后:

this.skill = skill;

初始化:

Teacher

新增状态。


2.10 this(...) 又是什么

前面学习:

this.name

表示当前对象中的成员。

但是在构造器中还存在一种特殊语法:

this(...)

例如:

public class Student {

    private String name;
    private String schoolName;

    public Student(String name) {
        this(name, "山西大学");
    }

    public Student(
            String name,
            String schoolName
    ) {
        this.name = name;
        this.schoolName = schoolName;
    }
}

其中:

this(name, "山西大学");

不是:

this.name

也不是普通方法调用。

它表示:

调用当前类中的另一个构造器。


2.11 this(...) 解决什么问题

假设需要两个创建方式:

new Student("张三");

以及:

new Student("李四", "山西大学");

业务规则规定:

如果没有指定学校,则默认学校是“山西大学”。

可以直接:

public Student(String name) {
    this.name = name;
    this.schoolName = "山西大学";
}

另一个构造器:

public Student(String name, String schoolName) {
    this.name = name;
    this.schoolName = schoolName;
}

但是:

this.name = name;

出现了重复。

可以改成:

public Student(String name) {
    this(name, "山西大学");
}

于是:

Student(String name)
       ↓
this(name, "山西大学")
       ↓
Student(String name, String schoolName)
       ↓
统一完成初始化

这样可以:

让多个构造器复用同一个初始化入口。


2.12 this(...) 与 super(...) 的区别

这是本章必须闭卷掌握的表格:

| 语法 | 调用对象 | 作用 | | ------------ | ---------------- | -------------- | | this(...) | 当前类其他构造器 | 构造器之间复用 | | super(...) | 直接父类构造器 | 初始化父类部分 |

例如:

this(name, "山西大学");

寻找的是:

当前类
Student(...)

而:

super(name, sex);

寻找的是:

直接父类
People(...)

千万不要混淆。


2.13 this(...) 和 this.xxx 完全不同

this.xxx

this.name = name;

这里:

this

表示当前对象。


this(...)

this(name, "山西大学");

这里是:

当前类其他构造器的显式调用。

因此:

this.name

和:

this(...)

虽然都使用 this,但语法用途完全不同。


2.14 super.xxx 和 super(...) 也不同

前一章学习:

super.run();

表示:

调用父类版本的成员方法。

例如:

super.name

表示父类视角成员。

而本章:

super(name);

表示:

调用直接父类构造器。

对比:

super.name
super.run()
    ↓
父类成员访问

super(...)
    ↓
父类构造器调用

2.15 JDK 21:this(...) 和 super(...) 必须放在哪里

在本课程的 JDK 21 基线中:

显式构造器调用必须位于构造器主体的第一条语句。

例如:

public Teacher(String name) {
    super(name);
    System.out.println("Teacher");
}

合法。

但:

public Teacher(String name) {
    System.out.println("Teacher");
    super(name);
}

在 JDK 21 中不能编译。

同样:

public Student(String name) {
    this(name, "山西大学");
}

必须把:

this(...)

放在第一条语句。


2.16 为什么 this(...) 与 super(...) 不能同时写

错误:

public Student(String name) {
    this(name, "山西大学");
    super();
}

同样错误:

public Student(String name) {
    super();
    this(name, "山西大学");
}

因为在 JDK 21 中:

this(...)

和:

super(...)

都要求作为显式构造器调用出现在构造器第一条语句。

第一条语句只能有一条。

因此同一个构造器不能同时显式写:

this(...)
+
super(...)

2.17 使用 this(...) 后父类构造器还会执行吗

会。

这是非常重要的理解。

例如:

class Parent {

    public Parent() {
        System.out.println("Parent");
    }
}
class Child extends Parent {

    public Child() {
        this(10);
        System.out.println("Child()");
    }

    public Child(int number) {
        System.out.println("Child(int)");
    }
}

第二个构造器:

Child(int number)

没有显式写 this(...)super(...)

因此实际上会先执行:

super();

所以调用:

new Child();

构造器链可以理解为:

Child()
   ↓ this(10)
Child(int)
   ↓ 隐式 super()
Parent()
   ↓
返回 Child(int)
   ↓
返回 Child()

因此:

this(...) 并不会让父类构造过程消失。

它只是把“调用父类构造器”的责任沿着当前类构造器链传递下去。


2.18 构造器链是什么

构造器链(Constructor Chaining)可以理解为:

创建一个对象时,通过 this(...)super(...) 形成的一系列构造器调用关系。

例如:

Object()
   ↑
People()
   ↑
Teacher(String,char,String)
   ↑
Teacher()

或者:

new Teacher()
     ↓
Teacher()
     ↓ this(...)
Teacher(String,char,String)
     ↓ super(...)
People(String,char)
     ↓ 隐式 super()
Object()

实际执行时,最上层父类构造逻辑先完成,再逐层返回执行子类构造器剩余代码。

所以经常可以观察到:

Object
  ↓
父类
  ↓
子类

这样的构造顺序。


2.19 构造器链最终到哪里

Java 普通类最终位于:

Object

继承体系中。

因此一个完整子类构造过程最终会追溯到:

Object()

例如:

Object
  ↑
Animal
  ↑
Dog
  ↑
Husky

创建:

new Husky();

最终会形成类似:

Husky(...)
   ↓
Dog(...)
   ↓
Animal(...)
   ↓
Object()

的构造链。


2.20 不能使用 this(...) 形成递归调用

观察:

public class Student {

    public Student() {
        this(10);
    }

    public Student(int age) {
        this();
    }
}

形成:

Student()
   ↓
Student(int)
   ↓
Student()
   ↓
Student(int)
   ↓
...

这不是普通运行时无限递归。

Java 编译器会检测这种构造器直接或间接调用自己的情况,并报告:

编译错误。

构造器调用链必须最终能够到达某个有效的:

super(...)

路径,而不能形成环。


三、使用方法

3.1 子类复用父类构造器初始化公共状态

父类:

public class Employee {

    private String id;
    private String name;
    private double salary;

    public Employee(
            String id,
            String name,
            double salary
    ) {
        this.id = id;
        this.name = name;
        this.salary = salary;
    }

    public String getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public double getSalary() {
        return salary;
    }
}

子类:

public class Programmer extends Employee {

    private String language;

    public Programmer(
            String id,
            String name,
            double salary,
            String language
    ) {
        super(id, name, salary);
        this.language = language;
    }

    public String getLanguage() {
        return language;
    }
}

这里:

super(id, name, salary);

非常重要。

它明确告诉父类:

你定义的状态由你自己的构造器负责初始化。


3.2 使用 this(...) 提供默认值

public class User {

    private String username;
    private String role;

    public User(String username) {
        this(username, "USER");
    }

    public User(
            String username,
            String role
    ) {
        this.username = username;
        this.role = role;
    }
}

使用:

User u1 = new User("LingXi");

User u2 =
        new User(
                "Admin",
                "ADMIN"
        );

这里:

u1

自动使用默认:

USER

而:

u2

可以显式指定角色。


3.3 同时存在 this(...) 与 super(...) 的正确结构

虽然同一个构造器不能同时显式写两者,但不同构造器之间可以形成:

public class Teacher extends People {

    private String skill;

    public Teacher(String name) {
        this(name, '男', "Java");
    }

    public Teacher(
            String name,
            char sex,
            String skill
    ) {
        super(name, sex);
        this.skill = skill;
    }
}

构造链:

Teacher(String)
      ↓
this(...)
      ↓
Teacher(String,char,String)
      ↓
super(...)
      ↓
People(String,char)

这是非常经典的组合。


四、原理与进阶

4.1 “子类构造器先调用父类构造器”要准确理解

原课程常用一句话:

子类的全部构造器都会先调用父类构造器,再执行自己。

作为入门理解基本正确。

但更准确地说:

每一个子类构造过程最终都必须通过构造器链调用直接父类构造器;某个具体构造器可能先显式 this(...) 调用本类另一个构造器,而不是自己直接写 super(...)

例如:

public Teacher(String name) {
    this(name, "Java");
}

这个构造器首先直接调用的是:

Teacher(String,String)

不是:

People(...)

但后者最终还必须调用父类构造器。


4.2 为什么父类构造器不能被继承

构造器本质上负责:

初始化声明它的类对应的对象构造过程。

父类构造器名称:

People(...)

子类构造器名称:

Teacher(...)

二者属于不同类。

因此 Java 采用:

super(...)

明确让子类构造过程调用父类构造过程,而不是把父类构造器本身“继承成子类构造器”。


4.3 super(...) 不是创建一个父类对象

这是重要误区。

执行:

super(name, sex);

并不是:

new People(name, sex);

它不是额外创建一个独立 People 对象。

它是在:

当前正在创建的子类对象的构造过程中执行直接父类构造器。

所以:

Teacher teacher = new Teacher(...);

最终仍然只是在创建这个:

Teacher 对象

不是:

一个 People 对象
+
一个 Teacher 对象

拼起来。


4.4 JDK 21 与更新 Java 版本要区分

本教程严格以:

JDK 21

为教学基线。

所以必须掌握:

显式 this(...) / super(...)
必须位于构造器第一条语句

后续 Java 版本曾探索“Flexible Constructor Bodies”等语言变化,但不属于 JDK 21 主线规则。

因此当前学习、考试和本教程代码全部按照:

JDK 21 规则

处理。


五、实践应用

5.1 异常类以后也会大量看到 super(...)

未来学习自定义异常时,会看到:

public class AgeIllegalException
        extends Exception {

    public AgeIllegalException() {
    }

    public AgeIllegalException(String message) {
        super(message);
    }
}

为什么调用:

super(message);

因为异常信息:

message

属于父类异常体系已经定义好的状态。

子类可以直接复用父类构造器完成初始化。

所以本章的:

super(...)

不是孤立语法。

以后在:

  • 自定义异常;
  • GUI 类继承;
  • 框架基类;
  • Java 类库扩展;

中都会反复出现。


5.2 继承体系的初始化职责

良好的设计通常倾向于:

父类构造器
负责父类自己的状态

子类构造器
通过 super(...) 初始化父类状态
再初始化子类自己的状态

例如:

Card
├── carId
├── owner
└── balance
    ↑
GoldCard
└── discount

那么:

public GoldCard(
        String carId,
        String owner,
        double balance,
        double discount
) {
    super(carId, owner, balance);
    this.discount = discount;
}

逻辑就非常清晰。


六、常见问题

6.1 子类构造器会继承父类构造器吗?

不会。

构造器不能被继承。


6.2 为什么创建子类时父类构造器会执行?

因为子类对象包含继承体系中的父类状态,需要按照 Java 构造规则先完成父类构造过程。


6.3 每个子类构造器都必须手写 super() 吗?

不用。

如果没有显式 this(...)super(...),JDK 21 会隐式按照:

super();

处理。


6.4 父类没有无参构造器怎么办?

子类必须选择一个真实存在、可访问且参数匹配的父类构造器:

super(...);

或者通过:

this(...)

调用当前类另一个最终能够正确调用父类构造器的构造器。


6.5 super(...) 调用祖父类构造器吗?

普通:

super(...)

直接调用:

当前类的直接父类构造器。

父类再负责继续调用它自己的父类。


6.6 this(...) 调用谁?

调用:

当前类中的另一个构造器。


6.7 this(...) 与 this.name 一样吗?

不一样。

this.name

访问当前对象成员。

this(...)

调用当前类另一个构造器。


6.8 super(...) 与 super.method() 一样吗?

不一样。

super(...)

调用父类构造器。

super.method()

调用父类成员方法实现。


6.9 this(...) 和 super(...) 为什么不能同时写?

JDK 21 要求显式构造器调用位于第一条语句。

同一个构造器不可能同时存在两个第一条语句。


6.10 this(...) 后父类构造器还执行吗?

执行。

当前类构造器链最终必须继续到父类构造过程。


6.11 this(...) 可以互相无限调用吗?

不能。

直接或间接形成构造器调用环会产生编译错误。


6.12 super(...) 会额外创建一个父类对象吗?

不会。

它是当前子类对象构造过程的一部分。


七、练习与验收

7.1 知识问答

  1. 构造器能否被继承?
  2. 创建子类对象时为什么需要执行父类构造器?
  3. 子类构造器默认第一步会尝试什么?
  4. super() 表示什么?
  5. super(参数) 表示什么?
  6. 父类只有有参构造器时,子类为什么可能报错?
  7. 如何解决父类没有无参构造器的问题?
  8. this(...) 表示什么?
  9. this(...)this.xxx 有什么区别?
  10. super(...)super.xxx 有什么区别?
  11. JDK 21 中显式构造器调用必须放在哪里?
  12. 为什么同一个构造器不能同时显式写 this(...)super(...)
  13. 使用 this(...) 后父类构造器还会不会执行?
  14. 什么是构造器链?
  15. Java 构造器链最终通常会到达哪个根类?
  16. 为什么 this(...) 不能形成循环?
  17. super(...) 是否意味着创建了一个独立父类对象?
  18. 为什么父类自己的字段通常适合由父类构造器初始化?

7.2 代码阅读

不要运行:

class A {

    public A() {
        System.out.println("A");
    }
}

class B extends A {

    public B() {
        System.out.println("B");
    }
}

class C extends B {

    public C() {
        System.out.println("C");
    }
}

public class Test {

    public static void main(String[] args) {
        new C();
    }
}

回答:

  1. 三个构造器按照什么顺序执行?
  2. B() 中隐式存在什么?
  3. C() 中隐式存在什么?
  4. 完整构造器链是什么?

继续分析:

class Parent {

    public Parent(int number) {
    }
}

class Child extends Parent {

    public Child() {
    }
}

回答:

  1. 是否可以编译?
  2. Child() 隐式尝试调用哪个构造器?
  3. 父类是否存在该构造器?
  4. 应该如何修复?

再阅读:

class Student {

    public Student() {
        this(18);
        System.out.println("A");
    }

    public Student(int age) {
        System.out.println("B");
    }
}

回答:

  1. new Student() 先进入哪个构造器?
  2. this(18) 调用哪个构造器?
  3. 哪个构造器最终负责调用 super()
  4. "A""B" 谁先输出?

7.3 手写代码

任务一:People / Teacher

设计:

People
├── name
└── sex

Teacher extends People
└── skill

要求:

  • People 提供无参构造器。
  • People 提供完整有参构造器。
  • Teacher 提供完整有参构造器。
  • Teacher 必须通过 super(...) 初始化父类状态。
  • 不允许为了方便把父类字段改成 public。

任务二:默认学校

设计:

Student
├── name
└── schoolName

要求:

new Student("张三");

默认学校:

山西大学

并允许:

new Student("李四", "北京大学");

必须使用:

this(...)

减少重复初始化代码。


任务三:三层构造器链

设计:

Person
   ↑
Employee
   ↑
Programmer

为每个类设计构造器,并在构造器中打印:

Person constructor
Employee constructor
Programmer constructor

创建 Programmer,预测执行顺序后再运行验证。

7.4 Debug

下面代码有什么问题?

class Parent {

    public Parent(String name) {
    }
}

class Child extends Parent {

    public Child() {
        System.out.println("Child");
    }
}

要求:

  1. 不运行,解释编译失败原因。
  2. 找出编译器隐式尝试的调用。
  3. 给出合理修复方案。

下面代码有什么问题?

public Student(String name) {
    System.out.println(name);
    this(name, "山西大学");
}

要求:

  1. 是否符合 JDK 21?
  2. 哪条规则被违反?
  3. 修复代码。

下面代码:

public Student() {
    this(18);
}

public Student(int age) {
    this();
}

要求:

  1. 找出调用链。
  2. 为什么不能编译?
  3. 如何重新设计构造器关系?

7.5 综合训练

设计一个:

Card
├── GoldCard
└── SilverCard

Card 包含:

cardId
owner
balance

两个子类分别增加自己的折扣信息。

要求:

  1. 父类负责公共状态构造。
  2. 子类使用 super(...) 初始化父类状态。
  3. 每个子类至少提供两个构造器。
  4. 其中一个构造器使用 this(...) 调用另一个构造器。
  5. 画出完整构造器调用链。
  6. 说明每一层构造器分别负责初始化什么。

7.6 本章验收

关闭资料与代码提示,确认自己能够:

  • [ ] 解释为什么创建子类先完成父类构造。
  • [ ] 手写 super()
  • [ ] 手写 super(...)
  • [ ] 手写 this(...)
  • [ ] 区分 this.xxxthis(...)
  • [ ] 区分 super.xxxsuper(...)
  • [ ] 判断父类没有无参构造器造成的编译错误。
  • [ ] 解释 JDK 21 中第一条语句规则。
  • [ ] 解释为什么 this(...)super(...) 不能同时显式出现。
  • [ ] 画出三层构造器链。
  • [ ] 识别构造器循环调用。
  • [ ] 从零完成 People → Teacher 的构造初始化。

如果你看到:

super(...)

只知道它“和继承有关”,却无法画出完整构造器链,那么本章还没有真正掌握。