为什么声明子类对象时会调用父类的构造函数?

40

考虑以下代码:

class Test {
    Test() {
        System.out.println("In constructor of Superclass");
    }

    int adds(int n1, int n2) {
        return(n1+n2);
    }

    void print(int sum) {
        System.out.println("the sums are " + sum);
    }
}


class Test1 extends Test {
    Test1(int n1, int n2) {
        System.out.println("In constructor of Subclass");
        int sum = this.adds(n1,n2);
        this.print(sum);
    }

    public static void main(String[] args) {
        Test1 a=new Test1(13,12);
        Test c=new Test1(15,14);
    }
}

如果父类中有构造函数,那么每次我们为子类构造对象时都会调用它(例如,针对类Test1的对象a将调用Test1(int n1, int n2)以及其父类的Test()构造函数)。

为什么会这样呢?

此程序的输出结果为:

在超类构造函数中

在子类构造函数中

总和为25

在超类构造函数中

在子类构造函数中

总和为29

18个回答

41

这将确保当调用构造函数时,它可以依赖于其超类中的所有字段都被初始化。

请参见此处第3.4.4节。


24
是的。在构建派生类之前,必须先构建超类,否则在派生类中应该可用的某些字段可能未被初始化。
一个小提示: 如果您必须显式调用超类构造函数并传递一些参数:
baseClassConstructor(){
    super(someParams);
}

那么超类构造函数必须是派生类构造函数中的第一个方法调用。例如,以下代码无法编译:

baseClassConstructor(){
     foo(); 
     super(someParams); // compilation error
}

我认为你第二句话中的第二个“super”应该是“derived”。(“在构建超级类之前必须先构建一个超类...”) - Dave Sherohman

11

编译器会自动为每个类构造函数添加super()。

众所周知,编译器会自动提供默认的构造函数,但它还将super()添加为第一条语句。如果您创建自己的构造函数并且没有this()或super()作为第一条语句,编译器将提供super()作为构造函数的第一条语句

输入图像描述


4

Java类的实例化顺序如下:

(在类加载时) 0. 静态成员和静态初始化块的初始器按声明顺序执行。

(每个新对象)

  1. 为构造函数参数创建局部变量
  2. 如果构造函数以调用该类的另一个构造函数开始,评估参数并递归到上一步。在继续之前完成该构造函数的所有步骤,包括构造函数调用的进一步递归。
  3. 如果上述步骤没有构建超类,则构建超类(如果未指定,则使用无参构造函数)。与第2步类似,对于超类的所有这些步骤进行处理,包括构造其超类,然后才能继续。
  4. 实例变量和非静态初始化块的初始程序按声明顺序执行。
  5. 构造函数的其余部分。

2
这就是Java的工作原理。如果你创建了一个子对象,那么父类构造函数会被(隐式地)调用。

9
准确来说,指的是默认的父类构造函数。如果在父类中没有定义默认构造函数,并且在子类的构造函数中没有显式调用super(args)(第一行),则编译将失败。 - heikkim

2
简单来说,如果超类有参数化构造函数,则需要在子类构造函数的第一行显式调用super(params),否则隐式调用所有超类构造函数,直到达到object类。

1
在子类的默认构造函数中存在默认的super()调用。
 //Default constructor of subClass
    subClass() {
    super();
    }

1
子类从其父类继承字段,并且这些字段必须被构造/初始化(构造函数的通常目的是初始化类成员,以便实例按照要求工作。我们知道有些人在这些可怜的构造函数中放置了更多功能...)。

1

构造函数实现了使对象准备工作的逻辑。对象可能在私有字段中保存状态,因此只有其类的方法才能访问它们。因此,如果您希望调用构造函数后您的子类实例真正准备好工作(即包括从基类继承的所有功能都可以正常使用),则必须调用基类的构造函数。

这就是系统为什么以这种方式工作的原因。

默认情况下会自动调用基类的默认构造函数。如果您想更改此设置,则必须在子类构造函数的第一行中编写super()来显式调用基类的构造函数。


1
当我们创建子类对象时,必须考虑在超类中定义的所有成员函数和成员变量。可能会出现这样一种情况,即某些成员变量在某些超类构造函数中被初始化。因此,当我们创建子类对象时,相应继承树中的所有构造函数都按自上而下的方式调用。 具体来说,当一个变量被定义为protected时,无论子类是否在同一个包中,它都将始终可在子类中访问。现在,如果我们从子类调用超类函数来打印这个受保护变量的值(该变量可能在超类的构造函数中初始化),我们必须得到正确的初始化值。因此,所有超类构造函数都会被调用。
在内部,Java在每个构造函数中调用super()。因此,每个子类构造函数都使用super()调用其父类构造函数,因此它们以自上而下的方式执行。
注意:函数可以被覆盖,但变量不行。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接