×

14. 继承进阶:super 关键字与构造器链

上一篇文章我们认识了继承的基本概念:用 extends 建立父子类关系,子类继承父类的成员,实现代码复用。但有个细节你可能已经注意到了——当我们创建子类对象时,父类的构造器似乎也被触发了?子类能不能调用父类的构造器?如果父子类有同名成员,子类怎么明确地访问父类的版本?

这一篇就来深入这些”进阶”问题,核心就是三个字——super

一、super 是什么?

super 是 Java 提供的一个关键字,用来引用当前对象的直接父类部分。它有两个主要用途:

  • 访问父类的成员(字段、方法)——当子类重写或隐藏了父类成员时
  • 调用父类的构造器——在子类构造器中,通过 super(...) 调用父类构造器

你可以把 super 理解成”父类版的 this“,它指明了”我要的是父类的那个版本”。

1. super 访问父类成员

当子类定义了与父类同名的字段或方法时,子类中的名字会遮蔽(hide)或重写(override)父类的版本。这时候如果想访问父类的版本,就要用 super

class Animal {
    String name = "动物";

    void speak() {
        System.out.println("动物发出声音");
    }
}

class Dog extends Animal {
    String name = "小狗"; // 遮蔽了父类的 name

    @Override
    void speak() {
        super.speak(); // 调用父类的 speak()
        System.out.println("汪汪!");
    }

    void showName() {
        System.out.println("子类 name:" + this.name);     // "小狗"
        System.out.println("父类 name:" + super.name);    // "动物"
    }
}

运行 showName() 会输出:

子类 name:小狗
父类 name:动物

注意:字段访问是由声明类型决定的,不是由实际类型决定的。但方法调用是动态绑定的-一别急,这个在下一篇《方
法重写》里会详细讲。

二、构造器链:子类对象创建时发生了什么?

这是继承中最重要的机制之一。Java 保证:创建任何子类对象时,一定会先调用父类的构造器,从最顶层的父类一直往下,最后才执行子类构造器的代码体。这条调用链条就叫构造器链(Constructor Chaining)。

1. 隐式调用:super()

如果你在子类构造器中没有写 super(...),编译器会自动插入一个无参的 super() 调用:

class Animal {
    public Animal() {
        System.out.println("Animal 构造器执行");
    }
}

class Dog extends Animal {
    public Dog() {
        // 这里等价于:super();  // 编译器自动添加
        System.out.println("Dog 构造器执行");
    }
}

public class Main {
    public static void main(String[] args) {
        Dog dog = new Dog();
    }
}

输出结果:

Animal 构造器执行
Dog 构造器执行

看到没?先父后子。从继承的角度解释:子类对象的内存结构中,父类的成员变量是”基底”,必须先初始化好,子类的扩展部分才能在此基础上搭建。

2. 显式调用:super(参数)

如果父类没有无参构造器(只有一个有参构造器),那么子类构造器必须显式调用 super(参数),否则编译报错。

class Animal {
    String name;

    // 只有有参构造器,没有无参构造器
    public Animal(String name) {
        this.name = name;
        System.out.println("Animal 构造器:" + name);
    }
}

class Dog extends Animal {
    public Dog(String name) {
        super(name);  // 必须显式调用,否则编译错误
        System.out.println("Dog 构造器:" + name);
    }
}

输出:

Animal 构造器:旺财
Dog 构造器:旺财

重要规则:superQ或super(参数)必须是子类构造器中的第一条语句。否则会报编译错误。这条规则保证了父类的初始
化一定在子类之前。

3. 完整的构造器链执行顺序

结合多层继承来看:

class GrandParent {
    public GrandParent() {
        System.out.println("1. GrandParent");
    }
}

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

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

public class Main {
    public static void main(String[] args) {
        new Child();
    }
}

输出:

1. GrandParent
2. Parent
3. Child

执行流程就像叠罗汉:从最顶层到最底层,构造器链逐级向下执行。

三、this() 与 super() 的互斥

你可能还记得 this() 可以在同一个类中调用另一个构造器。但 this()super() 不能同时出现在同一个构造器中——因为两者都必须是第一条语句,不可能同时满足。

不过,可以这样链式调用:

class Animal {
    String name;
    int age;

    public Animal(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

class Dog extends Animal {
    String breed;

    public Dog(String name, int age, String breed) {
        super(name, age);  // 调用父类构造器
        this.breed = breed;
    }

    public Dog(String name) {
        this(name, 0, "未知");  // this() 调用本类另一个构造器
        // this 内部最终会通过 super() 或 super(...) 调用父类构造器
    }
}

上面的 Dog(String name) 先调用了 this(name, 0, "未知"),而那个三参构造器里又调用了 super(name, age)。最终父类构造器仍然最先被执行。

四、多层继承(继承链)

Java 不允许多继承(一个类 extends 多个父类),但支持多层继承——A → B → C → D……形成一个继承链。

class Vehicle {
    void start() {
        System.out.println("交通工具启动");
    }
}

class Car extends Vehicle {
    void drive() {
        System.out.println("开车上路");
    }
}

class SUV extends Car {
    void offRoad() {
        System.out.println("越野模式");
    }
}

SUV 继承自 Car,Car 继承自 Vehicle。所以 SUV 对象可以:

  • 调用自己的 offRoad()
  • 调用父类的 drive()
  • 调用祖父类的 start()

这就是继承的传递性。在实践中,继承链通常建议不超过 3~4 层,太深会增加维护成本和理解难度。

五、访问修饰符在继承中的影响

不同的访问修饰符决定了子类能访问多少父类的成员:

修饰符本类同包子类(不同包)任意类
private
默认(无修饰符)
protected
public

protected 是继承场景中最特殊的一个:它给子类开了权限,但对外部仍然封闭。如果一个字段预期会被子类使用,就声明为 protected

class Animal {
    private int id;          // 子类不可见
    String type;             // 同包子类可见,不同包子类不可见
    protected int age;       // 子类可见
    public String name;      // 全部可见

    private void secret() {} // 子类不可调用
    protected void eat() {}  // 子类可调用
}

六、常见误区与最佳实践

❌ 误区 1:忘记写 super() 导致父类无参构造器不存在时报错

如果父类只定义了有参构造器,子类必须显式 super(...)。最佳实践:建议每个类都显式声明无参构造器(除非确实不需要通过无参方式创建)。

❌ 误区 2:在普通方法中使用 super()

super()this() 只能用在构造器里。普通方法中访问父类成员直接用 super.成员名,不能加括号。

❌ 误区 3:滥用多层继承

继承链过长 → 耦合度过高 → 改底层影响一大片。能用组合就用组合,这个后面会专门讲。

七、代码实战:员工管理系统

来一个综合案例,把今天学的知识串起来。

// 父类
class Employee {
    protected String name;
    protected double salary;

    public Employee(String name, double salary) {
        this.name = name;
        this.salary = salary;
        System.out.println("Employee 构造器:" + name);
    }

    public void work() {
        System.out.println(name + " 正在工作");
    }

    public double getSalary() {
        return salary;
    }
}

// 子类:经理
class Manager extends Employee {
    private double bonus;

    public Manager(String name, double salary, double bonus) {
        super(name, salary);  // 调用父类构造器
        this.bonus = bonus;
        System.out.println("Manager 构造器:" + name);
    }

    @Override
    public void work() {
        super.work();  // 调用父类的 work
        System.out.println(name + " 在管理团队");
    }

    @Override
    public double getSalary() {
        return super.getSalary() + bonus;  // 调用父类方法获取基础薪资再加奖金
    }
}

// 子类:程序员
class Programmer extends Employee {
    private String skill;

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

    @Override
    public void work() {
        super.work();
        System.out.println(name + " 在写 " + skill + " 代码");
    }
}

public class Main {
    public static void main(String[] args) {
        Manager mgr = new Manager("张三", 20000, 5000);
        System.out.println("---");
        Programmer coder = new Programmer("李四", 15000, "Java");

        System.out.println("n=== 工作 ===");
        mgr.work();
        System.out.println("---");
        coder.work();

        System.out.println("n=== 薪资 ===");
        System.out.println(mgr.name + ":" + mgr.getSalary());
        System.out.println(coder.name + ":" + coder.getSalary());
    }
}

运行结果:

Employee 构造器:张三
Manager 构造器:张三
---
Employee 构造器:李四

=== 工作 ===
张三 正在工作
张三 在管理团队
---
李四 正在工作
李四 在写 Java 代码

=== 薪资 ===
张三:25000.0
李四:15000.0

这个案例清晰地展示了:

  • 构造器链的执行顺序(先父后子)
  • super() 调用父类构造器传参
  • super.方法() 复用父类逻辑
  • protected 让子类可以直接访问父类字段
  • 方法重写配合 super 实现”扩展而非替换”

结语

这一篇我们从 super 关键字出发,深入了几重继承的关键机制——构造器链的执行顺序、super()this() 的关系、多层继承、访问修饰符在继承中的作用。这些都是 Java 继承中最容易出错但又最重要的基础。

下一篇我们将进入一个更精彩的话题——方法重写(Override)与 final 关键字,这是面向对象多态的核心前提。到时候我们会深入 Java 的动态绑定机制,看看 “重写” 和 “重载” 到底有什么区别。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

作者

2248768396@qq.com

相关文章

20. 接口基础:语法、特性与抽象类对比

抽象类讲完了。你知道父类可以只声明方法签名,...

读出全部

19. 多态的应用:参数多态化、工厂模式与支付系统实战

上一篇我们拆解了多态的底层机制——动态绑定、...

读出全部

18. 多态的本质:动态绑定、向上转型与向下转型

抽象类学完了,你现在应该知道 「父类只声明、...

读出全部