繁体   English   中英

为什么在声明子类的对象时会调用超类的构造函数? (爪哇)

[英]Why is constructor of super class invoked when we declare the object of sub class? (Java)

考虑这个代码:

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

因为它将确保在调用构造函数时,它可以依赖其超类中正在初始化的所有字段。

请参阅此处的3.4.4

是的。 必须先构造超类,然后才能构造派生类,否则派生类中应该可用的某些字段可能无法初始化。

一点注意:如果您必须显式调用超类构造函数并向其传递一些参数:

baseClassConstructor(){
    super(someParams);
}

那么超级构造函数必须是第一个调用派生构造函数的方法。 例如,这不会编译:

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

super() 由编译器自动添加到每个类构造函数中。

正如我们所知道的,默认构造函数是由编译器自动提供的,但它也为第一条语句添加了 super()。编译器将提供 super() 作为构造函数的第一条语句

在此处输入图片说明

Java 类按以下顺序实例化:

(在类加载时) 0. 静态成员和静态初始值设定项块的初始值设定项,按声明顺序。

(在每个新对象上)

  1. 为构造函数参数创建局部变量
  2. 如果构造函数以调用该类的另一个构造函数开始,则评估参数并递归到上一步。 在继续之前,该构造函数的所有步骤都已完成,包括构造函数调用的进一步递归。
  3. 如果上面没有构造超类,则构造超类(如果未指定,则使用无参数构造函数)。 与#2 一样,在继续之前,为超类完成所有这些步骤,包括构建 IT 的超类。
  4. 实例变量的初始值设定项和非静态初始值设定项块,按声明顺序排列。
  5. 构造函数的其余部分。

这就是 Java 的工作方式。 如果创建子对象,则(隐式)调用超级构造函数。

基类构造函数将在派生类构造函数之前调用。 这是有道理的,因为它保证在执行派生类的构造函数时正确构造基类。 这允许您在构造派生类期间使用来自基类的一些数据。

当我们创建子类的对象时,它必须考虑到超类中定义的所有成员函数和成员变量。 可能会出现在某些超类构造函数中初始化某些成员变量的情况。 因此,当我们创建子类对象时,相应继承树中的所有构造函数都以自顶向下的方式调用。

特别是当一个变量被定义为protected 时,它总是可以在子类中访问,而不管子类是否在同一个包中 现在从子类如果我们调用一个超类函数来打印这个受保护变量的值(可以在超类的构造函数中初始化),我们必须得到正确的初始化值。因此所有超类构造函数都被调用。

Java 内部在每个构造函数中调用 super() 。 所以每个子类构造函数都使用 super() 调用它的超类构造函数,因此它们以自上而下的方式执行。

注意:可以覆盖函数而不是变量。

由于您将基类属性继承到派生类中,因此在某些情况下,您的派生类构造函数可能需要一些基类变量来初始化其变量。 所以首先它必须初始化基类变量,然后是派生类变量。 这就是 Java 先调用基类构造函数,然后调用派生类构造函数的原因。

而且在不初始化父类的情况下初始化子类也没有任何意义。

超类的构造函数首先被调用,因为程序中的所有方法首先出现在堆中,编译后它们存储在堆栈中,因此首先调用超类构造函数。

简而言之,如果超类具有参数化构造函数,则需要在子类构造函数的第一行中显式调用 super(params) 否则隐式调用所有超类构造函数,直到到达对象类为止。

子类的默认构造函数中有一个默认的 super() 调用。

 //Default constructor of subClass
    subClass() {
    super();
    }

“如果构造函数没有显式调用超类构造函数,Java编译器会自动插入对超类的无参数构造函数的调用。如果超类没有无参数构造函数,你会得到一个编译时错误. Object 确实有这样的构造函数,所以如果 Object 是唯一的超类,那就没有问题了。”
(来源: https : //docs.oracle.com/javase/tutorial/java/IandI/super.html

我会尝试从不同的角度来回答这个问题。

假设 Java 没有自动为您调用超级构造函数。 如果继承该类,则必须隐式调用超级构造函数,或者自己重写它。 这将要求您对超类的工作原理有内部知识,这很糟糕。 它还需要重写代码,这也不好。

我同意在幕后调用超级构造函数有点不直观。 另一方面,我不确定他们如何以更直观的方式做到这一点。

我们知道一个类的成员变量(字段)必须在创建对象之前进行初始化,因为这些字段代表了对象的状态。 如果这些字段未明确初始化,则编译器通过调用无参数默认构造函数隐式提供它们的默认值。 这就是为什么子类构造函数调用超类无参数默认构造函数或由编译器隐式调用的原因。编译器不提供本地变量的默认值。

在这里,您将 Test 扩展到您的 test1 类,这意味着您可以访问 test1 中的所有测试方法和变量。 请注意,只有当内存分配给类方法或变量时,您才能访问类方法或变量,为此它需要一些默认或参数化的构造函数,因此编译器在这里发现它正在扩展一个类,它将尝试找到超类构造函数,以便您可以访问其所有方法。

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

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

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

自动调用基类的默认构造函数。 如果你想改变这一点,你必须通过在子类构造函数的第一行写super()来显式调用基类的构造函数。

父母先退出!! 和现实世界一样,如果没有父母,孩子就不能存在..所以首先初始化父母(超类)对于在孩子(子类)类中使用 thrm 很重要..

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM