杰瑞科技汇

Java构造函数中super必须显式调用吗?

构造函数

什么是构造函数?

构造函数是一种特殊的方法,用于在创建对象时初始化对象的状态,它具有以下特点:

Java构造函数中super必须显式调用吗?-图1
(图片来源网络,侵删)
  1. 名称与类名相同:构造函数的名称必须和它所在的类的名称完全一样。
  2. 没有返回类型:构造函数没有返回类型,甚至连 void 也没有,如果你写了返回类型,它就变成了一个普通的方法,而不是构造函数。
  3. 在创建对象时自动调用:当你使用 new 关键字创建一个对象时,构造函数会被自动调用。
  4. 可以重载:一个类可以有多个构造函数,只要它们的参数列表(参数的个数、类型或顺序)不同即可,这被称为构造函数重载。

构造函数的作用

构造函数的主要职责是初始化对象的成员变量,确保对象在被创建后处于一个合法、可用的初始状态。

示例:一个简单的构造函数

public class Person {
    // 成员变量
    private String name;
    private int age;
    // 1. 无参构造函数
    // 当你 new Person() 时,这个构造函数会被调用
    public Person() {
        System.out.println("Person 的无参构造函数被调用");
        // 提供默认值
        this.name = "未知";
        this.age = 0;
    }
    // 2. 带参构造函数 (重载)
    // 当你 new Person("张三", 25) 时,这个构造函数会被调用
    public Person(String name, int age) {
        System.out.println("Person 的带参构造函数被调用");
        // 为成员变量赋值
        this.name = name; // this.name 指的是当前对象的 name 成员变量
        this.age = age;
    }
    // 打印信息的方法
    public void displayInfo() {
        System.out.println("姓名: " + name + ", 年龄: " + age);
    }
    public static void main(String[] args) {
        System.out.println("创建第一个对象 p1:");
        Person p1 = new Person(); // 调用无参构造函数
        p1.displayInfo();
        System.out.println("\n创建第二个对象 p2:");
        Person p2 = new Person("李四", 30); // 调用带参构造函数
        p2.displayInfo();
    }
}

输出:

创建第一个对象 p1:
Person 的无参构造函数被调用
姓名: 未知, 年龄: 0
创建第二个对象 p2:
Person 的带参构造函数被调用
姓名: 李四, 年龄: 30

super 关键字

super 关键字在 Java 中有两个主要用途:

  1. 调用父类的构造函数
  2. 访问父类的成员(变量或方法)

在构造函数的上下文中,我们主要关注第一个用途。

Java构造函数中super必须显式调用吗?-图2
(图片来源网络,侵删)

super() 在构造函数中的作用

在 Java 中,构造一个子类对象时,必须先构造其父类对象,这是因为子类继承了父类的所有成员(包括私有的),这些成员需要被正确地初始化。

super() 语句就是用来显式地调用父类的构造函数的,它必须出现在子类构造函数的第一行。

规则:

  • 隐式调用:如果子类构造函数中没有显式地使用 super(...) 来调用父类的构造函数,Java 编译器会自动在子类构造函数的第一行插入一个对父类无参构造函数的调用,即 super()
  • 显式调用:如果你想在子类构造函数中调用父类的带参构造函数,你必须使用 super(参数列表) 的形式,并且它必须是构造函数的第一行。
  • 必须第一行super()this()(调用本类其他构造函数)都只能在构造函数的第一行出现,因此它们不能同时出现在同一个构造函数中。

super 和构造函数的详细示例

让我们通过一个经典的继承关系来理解这个过程。

Java构造函数中super必须显式调用吗?-图3
(图片来源网络,侵删)

场景:

Animal (动物) 是父类,Dog (狗) 是子类。

// 父类: Animal.java
public class Animal {
    private String name;
    // 父类的无参构造函数
    public Animal() {
        System.out.println("Animal 的无参构造函数被调用");
        this.name = "一只动物";
    }
    // 父类的带参构造函数
    public Animal(String name) {
        System.out.println("Animal 的带参构造函数被调用,名字是: " + name);
        this.name = name;
    }
    public void eat() {
        System.out.println(name + " 正在吃东西。");
    }
}
// 子类: Dog.java
public class Dog extends Animal {
    private String breed; // 品种
    // 子类的构造函数 1: 调用父类的无参构造函数
    public Dog() {
        // 这里的 super() 是隐式的,可以省略不写。
        // 编译器会自动加上。
        System.out.println("Dog 的无参构造函数被调用");
        this.breed = "未知品种";
    }
    // 子类的构造函数 2: 调用父类的带参构造函数
    public Dog(String name, String breed) {
        // 显式调用父类的带参构造函数 Animal(String name)
        // super() 必须是第一行!
        super(name);
        System.out.println("Dog 的带参构造函数被调用,品种是: " + breed);
        this.breed = breed;
    }
    public void displayInfo() {
        // 使用 super.eat() 来调用父类的方法
        super.eat(); 
        System.out.println("这是一只 " + breed + " 名叫 " + /*name 会报错,因为 name 是私有的*/);
    }
    // 如果想访问父类的私有成员,通常通过父类的公共方法
    public String getName() {
        // 无法直接访问 name,需要父类提供 getter
        // 假设父类有 public String getName() { return this.name; }
        // 这里为了演示,我们假设 Animal 类有 getName() 方法
        return super.getName(); // 调用父类的公共方法
    }
    public static void main(String[] args) {
        System.out.println("--- 创建第一个 Dog 对象 d1 ---");
        Dog d1 = new Dog(); // 会先调用 Animal(),再调用 Dog()
        d1.displayInfo();
        System.out.println("\n--- 创建第二个 Dog 对象 d2 ---");
        Dog d2 = new Dog("旺财", "中华田园犬"); // 会先调用 Animal("旺财"),再调用 Dog(...)
        d2.displayInfo();
    }
}

输出分析:

--- 创建第一个 Dog 对象 d1 ---
Animal 的无参构造函数被调用
Dog 的无参构造函数被调用
一只动物 正在吃东西。
这是一只 未知品种 名叫 ...
--- 创建第二个 Dog 对象 d2 ---
Animal 的带参构造函数被调用,名字是: 旺财
Dog 的带参构造函数被调用,品种是: 中华田园犬
旺财 正在吃东西。
这是一只 中华田园犬 名叫 ...

流程解析 (对于 new Dog("旺财", "中华田园犬")):

  1. new Dog(...) 被执行,JVM 准备创建 Dog 对象。
  2. 第一步:构造父类 Animal 对象。
    • Dog 的构造函数第一行是 super(name),即 super("旺财")
    • Dog 的构造函数暂停执行,转而去执行 Animal 类的 public Animal(String name) 构造函数。
    • 打印 Animal 的带参构造函数被调用,名字是: 旺财
    • Animal 对象的成员变量 name 被初始化为 "旺财"
    • Animal 的构造函数执行完毕。
  3. 第二步:回到 Dog 的构造函数继续执行。
    • 回到 Dog 的构造函数,super(name) 执行完毕。
    • 执行下一行:System.out.println("Dog 的带参构造函数被调用,品种是: " + breed);
    • Dog 对象的成员变量 breed 被初始化为 "中华田园犬"
    • Dog 对象构造完成。
概念 作用 关键点
构造函数 初始化对象自身状态。 名字与类相同,无返回类型,可重载。
super() 在子类构造函数中,调用父类的构造函数。 必须放在子类构造函数的第一行。
隐式 super() 如果子类构造函数没有 super(...),编译器会自动添加 super() 来调用父类的无参构造函数。 确保父类对象被先构造。
显式 super(...) 用于调用父类的特定带参构造函数,以便在创建子类对象时向父类传递初始化参数。 用于更灵活地控制父类的初始化过程。

记住这个核心原则:子类的构造过程,永远是父类在前,子类在后。 super 就是控制这个“父类在前”过程的工具。

分享:
扫描分享到社交APP
上一篇
下一篇