杰瑞科技汇

Java构造函数如何调用其他构造函数?

核心要点

在 Java 中,一个构造函数可以通过两种方式调用同一个类中的另一个构造函数:

Java构造函数如何调用其他构造函数?-图1
(图片来源网络,侵删)
  1. 显式调用(推荐):使用 this() 关键字。
  2. 隐式调用(编译器自动完成):使用 super() 关键字,用于调用父类的构造函数。

关键规则: 这种调用(无论是 this() 还是 super()必须是构造函数中的第一条语句,这意味着一个构造函数中不能同时调用 this()super(),因为它们都要求是第一条语句。


显式调用:使用 this()

当一个类有多个构造函数,并且它们之间存在共同的初始化逻辑时,为了避免代码重复,我们可以让一个构造函数调用另一个构造函数。

语法

this([argument_list]);

this() 必须出现在当前构造函数的第一行

示例

假设我们有一个 Student 类,它总是需要设置 name,我们可以让一个“全参数”的构造函数来完成核心初始化工作,然后其他构造函数(如只提供 name 的构造函数)通过 this() 来调用它。

Java构造函数如何调用其他构造函数?-图2
(图片来源网络,侵删)
public class Student {
    private String name;
    private int age;
    private String major;
    // 构造函数 3:全参数构造函数,核心初始化逻辑
    public Student(String name, int age, String major) {
        System.out.println("调用全参数构造函数");
        this.name = name;
        this.age = age;
        this.major = major;
    }
    // 构造函数 2:只提供 name 和 age,major 默认为 "未定"
    // 它通过 this() 调用构造函数 3
    public Student(String name, int age) {
        // this() 必须是第一行!
        this(name, age, "未定"); 
        System.out.println("调用两参数构造函数");
    }
    // 构造函数 1:只提供 name,age 和 major 都使用默认值
    // 它通过 this() 调用构造函数 2
    public Student(String name) {
        // this() 必须是第一行!
        this(name, 18); // 调用上面的两参数构造函数
        System.out.println("调用单参数构造函数");
    }
    public void displayInfo() {
        System.out.println("姓名: " + name + ", 年龄: " + age + ", 专业: " + major);
    }
    public static void main(String[] args) {
        System.out.println("--- 创建学生1 (使用单参数构造函数) ---");
        Student s1 = new Student("张三");
        s1.displayInfo();
        System.out.println("\n--- 创建学生2 (使用两参数构造函数) ---");
        Student s2 = new Student("李四", 20);
        s2.displayInfo();
        System.out.println("\n--- 创建学生3 (使用全参数构造函数) ---");
        Student s3 = new Student("王五", 21, "计算机科学");
        s3.displayInfo();
    }
}

输出结果:

--- 创建学生1 (使用单参数构造函数) ---
调用全参数构造函数
调用两参数构造函数
调用单参数构造函数
姓名: 张三, 年龄: 18, 专业: 未定
--- 创建学生2 (使用两参数构造函数) ---
调用全参数构造函数
调用两参数构造函数
姓名: 李四, 年龄: 20, 专业: 未定
--- 创建学生3 (使用全参数构造函数) ---
调用全参数构造函数
姓名: 王五, 年龄: 21, 专业: 计算机科学

分析:

  • new Student("张三") 首先调用 Student(String name)
  • Student(String name) 中,this("张三", 18) 被执行,它立即调用 Student(String name, int age)
  • Student(String name, int age) 中,this("张三", 18, "未定") 被执行,它最终调用核心的 Student(String name, int age, String major) 来完成实际的字段赋值。
  • 这个调用链形成了一个“瀑布”效应,确保了无论通过哪个构造函数创建对象,核心的初始化逻辑都能被执行,同时保持了代码的简洁和可维护性。

隐式调用:使用 super()

当创建一个子类的对象时,Java 会确保父类的部分也被正确初始化,即使你没有写 super(),编译器也会在子类构造函数的第一行自动插入一个对父类无参构造函数 super() 的调用。

语法

super([argument_list]);

super() 也必须出现在构造函数的第一行

Java构造函数如何调用其他构造函数?-图3
(图片来源网络,侵删)

示例

// 父类
class Animal {
    private String species;
    // 父类的无参构造函数
    public Animal() {
        this.species = "未知物种";
        System.out.println("Animal 的无参构造函数被调用");
    }
    // 父类的带参构造函数
    public Animal(String species) {
        this.species = species;
        System.out.println("Animal 的带参构造函数被调用,物种是: " + species);
    }
}
// 子类
class Dog extends Animal {
    private String breed;
    // 子类的构造函数 1
    public Dog() {
        // 编译器会在这里自动插入 super(),所以可以省略不写
        this.breed = "混血";
        System.out.println("Dog 的无参构造函数被调用");
    }
    // 子类的构造函数 2
    public Dog(String breed, String species) {
        // 显式调用父类的带参构造函数
        super(species); 
        this.breed = breed;
        System.out.println("Dog 的带参构造函数被调用,品种是: " + breed);
    }
    public void display() {
        System.out.println("这是一只 " + breed + " 的狗,物种是 " + /* 这里需要访问父类字段,但 Animal 的字段是 private,所以我们需要提供 getter 方法 */);
    }
}

修正后的完整示例(添加 getter 方法):

// 父类
class Animal {
    private String species;
    public Animal() {
        this.species = "未知物种";
        System.out.println("Animal 的无参构造函数被调用");
    }
    public Animal(String species) {
        this.species = species;
        System.out.println("Animal 的带参构造函数被调用,物种是: " + species);
    }
    public String getSpecies() {
        return species;
    }
}
// 子类
class Dog extends Animal {
    private String breed;
    public Dog() {
        // super(); // 编译器自动添加,可以省略
        this.breed = "混血";
        System.out.println("Dog 的无参构造函数被调用");
    }
    public Dog(String breed, String species) {
        super(species); // 显式调用父类的带参构造函数
        this.breed = breed;
        System.out.println("Dog 的带参构造函数被调用,品种是: " + breed);
    }
    public void display() {
        System.out.println("这是一只 " + breed + " 的狗,物种是 " + this.getSpecies());
    }
}
public class Main {
    public static void main(String[] args) {
        System.out.println("--- 创建 Dog 对象 1 ---");
        Dog d1 = new Dog();
        d1.display();
        System.out.println("\n--- 创建 Dog 对象 2 ---");
        Dog d2 = new Dog("金毛", "家犬");
        d2.display();
    }
}

输出结果:

--- 创建 Dog 对象 1 ---
Animal 的无参构造函数被调用
Dog 的无参构造函数被调用
这是一只 混血 的狗,物种是 未知物种
--- 创建 Dog 对象 2 ---
Animal 的带参构造函数被调用,物种是: 家犬
Dog 的带参构造函数被调用,品种是: 金毛
这是一只 金毛 的狗,物种是 家犬

this()super() 的冲突与规则

如前所述,this()super() 都要求是构造函数的第一条语句,它们是互斥的。

规则总结:

  1. 调用父类构造函数:子类构造函数总是会调用父类的某个构造函数,如果你没有显式地写 super(...),编译器会自动为你添加一个 super(),调用父类的无参构造函数。
  2. 调用本类构造函数:如果你想在一个构造函数中调用另一个构造函数,你必须使用 this(...)
  3. 互斥性:因为 this()super() 都必须是构造函数的第一条语句,所以你不能在同一个构造函数中同时使用它们。

如果父类没有无参构造函数会发生什么?

这是一个非常常见的编译错误,如果你没有在子类中显式调用 super(...),编译器会尝试自动调用 super(),但如果父类没有定义无参构造函数,编译器就会失败,因为它找不到可以调用的无参构造函数。

解决方案: 在子类的构造函数中,你必须显式地调用父类中存在的带参构造函数。

class Parent {
    // 父类只有带参构造函数,没有无参构造函数!
    public Parent(String name) {
        System.out.println("Parent 的带参构造函数被调用");
    }
}
class Child extends Parent {
    // 编译错误!因为 Child() 构造函数会隐式调用 super(),
    // 但 Parent 类没有无参构造函数。
    // public Child() {
    //     // ...
    // }
    // 正确的做法:显式调用父类的带参构造函数
    public Child() {
        super("由 Child 传入的默认名"); // 必须是第一行!
        System.out.println("Child 的无参构造函数被调用");
    }
}

最佳实践与注意事项

  1. 保持构造函数链清晰:构造函数的调用链应该像瀑布一样,从最基础的构造函数(通常是参数最多的那个)流向更具体的构造函数,这有助于理解对象的初始化过程。
  2. 避免过度使用:虽然 this() 很有用,但如果滥用(比如构造函数调用链过长),会使代码难以理解和维护,通常建议不超过 2-3 层的调用。
  3. 总是考虑父类:在设计子类时,要时刻记得父类的构造函数,确保父类有合适的构造函数可以被调用,或者显式地使用 super() 调用。
  4. 文档化:如果一个构造函数依赖于另一个构造函数的初始化逻辑,最好在注释中说明,方便其他开发者阅读。
关键字 作用 调用对象 位置要求 目的
this() 调用本类的另一个构造函数 当前类 必须是构造函数的第一条语句 代码复用,避免重复初始化逻辑
super() 调用父类的构造函数 父类 必须是构造函数的第一条语句 初始化继承自父类的成员,确保父类对象被正确创建

理解并熟练运用 this()super() 是掌握 Java 面向对象编程的关键一步,它能帮助你写出更健壮、更高效、更易于维护的代码。

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