杰瑞科技汇

Java super 构造函数如何调用与使用规则?

super() 的作用

super() 在构造函数中用于显式地调用父类的构造函数

Java super 构造函数如何调用与使用规则?-图1
(图片来源网络,侵删)
  1. 为什么需要它?

    • 在创建子类对象时,子类会继承父类的所有成员(字段和方法),为了确保父类部分也被正确地初始化,Java 规定:子类的构造函数在执行之前,必须先调用其父类的某个构造函数
    • 如果你在子类的构造函数中没有显式地使用 super() 来调用父类构造函数,Java 编译器会自动地在子类构造函数的第一行插入一个对父类无参构造函数的调用,即 super()
  2. 它做了什么?

    • super() 负责初始化从父类继承来的成员变量,它不会创建父类的对象,而是确保父类的那部分“遗产”被正确设置。

super() 的语法和规则

语法

super() 必须位于子类构造函数的第一行,这是最严格的规则之一。

public class Child extends Parent {
    public Child() {
        // super(); // 编译器会自动在这里添加这一行,如果没写的话
        // 子类的初始化代码...
    }
}

调用父类的特定构造函数

super() 不仅可以调用父类的无参构造函数,还可以调用父类的任何指定参数的构造函数,只要传入对应的实参即可。

Java super 构造函数如何调用与使用规则?-图2
(图片来源网络,侵删)
// 假设父类 Parent 有以下构造函数
class Parent {
    private String name;
    // 父类无参构造函数
    public Parent() {
        this.name = "Unknown";
        System.out.println("Parent's no-arg constructor is called.");
    }
    // 父类带参构造函数
    public Parent(String name) {
        this.name = name;
        System.out.println("Parent's arg constructor is called with name: " + name);
    }
}
// 子类 Child
class Child extends Parent {
    public Child() {
        // 默认情况下,这里等同于 super()
        System.out.println("Child's no-arg constructor is called.");
    }
    public Child(String name) {
        // 显式调用父类的带参构造函数 Parent(String name)
        super(name);
        System.out.println("Child's arg constructor is called.");
    }
}

必须放在第一行

super() 必须是构造函数中的第一条语句,这是因为它需要先完成父类的初始化,然后才能执行子类的初始化代码。

// 错误的示例
public class Child extends Parent {
    public Child() {
        System.out.println("This line comes before super() - COMPILE ERROR!");
        super(); // 错误!super() 必须是第一条语句
    }
}

工作流程和实例分析

让我们通过一个完整的例子来理解对象创建时的初始化顺序。

场景

  • Animal 是父类。
  • DogAnimal 的子类。

代码

// 父类
class Animal {
    private String type;
    // 父类无参构造函数
    public Animal() {
        this.type = "Generic Animal";
        System.out.println("Animal's no-arg constructor is called. Type is: " + this.type);
    }
    // 父类带参构造函数
    public Animal(String type) {
        this.type = type;
        System.out.println("Animal's arg constructor is called. Type is: " + this.type);
    }
}
// 子类
class Dog extends Animal {
    private String breed;
    // 子类无参构造函数
    public Dog() {
        // super(); // 编译器会自动在这里添加,调用父类的无参构造函数 Animal()
        this.breed = "Unknown Breed";
        System.out.println("Dog's no-arg constructor is called. Breed is: " + this.breed);
    }
    // 子类带参构造函数
    public Dog(String type, String breed) {
        // 显式调用父类的带参构造函数 Animal(String type)
        super(type);
        this.breed = breed;
        System.out.println("Dog's arg constructor is called. Breed is: " + this.breed);
    }
}
// 测试类
public class Main {
    public static void main(String[] args) {
        System.out.println("--- Creating a Dog object with no-arg constructor ---");
        Dog myDog1 = new Dog();
        System.out.println("\n------------------------------------\n");
        System.out.println("--- Creating a Dog object with arg constructor ---");
        Dog myDog2 = new Dog("Canine", "Golden Retriever");
        System.out.println("\n------------------------------------\n");
    }
}

输出结果分析

--- Creating a Dog object with no-arg constructor ---
Animal's no-arg constructor is called. Type is: Generic Animal
Dog's no-arg constructor is called. Breed is: Unknown Breed
------------------------------------
--- Creating a Dog object with arg constructor ---
Animal's arg constructor is called. Type is: Canine
Dog's arg constructor is called. Breed is: Golden Retriever
------------------------------------

流程解析:

  1. 创建 myDog1 时 (new Dog()):

    • new Dog() 被调用。
    • 因为 Dog 的构造函数中没有 super(),编译器自动添加 super()
    • 执行 super(): 调用 Animal 的无参构造函数,打印 Animal's no-arg constructor...
    • 返回到 Dog 的构造函数: 继续执行 this.breed = "Unknown Breed";System.out.println(...),打印 Dog's no-arg constructor...
  2. 创建 myDog2 时 (new Dog("Canine", "Golden Retriever")):

    • new Dog("Canine", "Golden Retriever") 被调用。
    • 第一条语句是 super(type),即 super("Canine")
    • 执行 super("Canine"): 调用 Animal 的带参构造函数 Animal(String type),打印 Animal's arg constructor...
    • 返回到 Dog 的构造函数: 继续执行 this.breed = "Golden Retriever";System.out.println(...),打印 Dog's arg constructor...

this() 的区别

super()this() 都必须在构造函数的第一行,因此它们不能同时出现在同一个构造函数中

特性 super() this()
含义 调用父类的构造函数 调用本类其他构造函数
目的 初始化从父类继承的成员 在本类内部提供构造函数重载,避免重复代码
示例 super("name"); this("name", 0);
class Example {
    private String name;
    private int id;
    public Example() {
        // 调用本类的另一个带参构造函数
        this("Default Name", 0);
        System.out.println("No-arg constructor called.");
    }
    public Example(String name, int id) {
        this.name = name;
        this.id = id;
        System.out.println("Arg constructor called.");
    }
}

重要注意事项

  1. 父类无参构造函数的重要性:如果你在父类中定义了一个或多个带参构造函数,但没有显式地定义一个无参构造函数,那么编译器不会再为你自动生成一个无参构造函数。

    • 在这种情况下,如果你在子类中没有使用 super(参数列表) 来调用一个存在的父类构造函数,而是试图使用默认的 super(),编译器会报错,因为它找不到可匹配的父类无参构造函数。
    // 错误的父类
    class ParentWithoutNoArg {
        public ParentWithoutNoArg(String name) {
            // ...
        }
        // 编译器不会自动添加 public ParentWithoutNoArg() {}
    }
    // 错误的子类
    class ChildError extends ParentWithoutNoArg {
        public ChildError() {
            // super(); // 编译错误!找不到符号: 父类构造函数 ParentWithoutNoArg()
            // 因为父类只有带参构造函数,没有无参构造函数
        }
    }
    // 正确的子类
    class ChildCorrect extends ParentWithoutNoArg {
        public ChildCorrect() {
            // 必须显式调用父类存在的构造函数
            super("Default Name");
        }
    }
  2. 构造链:这个调用关系可以一直向上追溯到 Object 类(Java 中所有类的终极父类),整个对象的初始化过程是一个“构造链”。

    • new GrandChild() -> Child() -> Parent() -> Object() (从下往上调用)
    • Object() -> Parent() -> Child() -> GrandChild() (从上往下执行构造函数体)
关键点 描述
核心作用 在子类构造函数中调用父类的构造函数,以初始化继承的成员。
位置 必须是子类构造函数的第一条语句
默认行为 如果子类构造函数中没有 super(),编译器会自动添加一个对父类无参构造函数的调用 super()
灵活性 可以通过 super(参数列表) 调用父类的任何指定构造函数。
this() 的关系 super()this() 不能同时出现在同一个构造函数中,因为它们都必须在第一行。
常见错误 当父类没有无参构造函数,而子类构造函数又没有显式调用父类的任何构造函数时,会导致编译错误。

理解 super() 在构造函数中的行为是掌握 Java 继承机制和对象生命周期的基础,务必记住它的“第一行”规则和与父类构造函数的紧密联系。

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