57

假设我有三个课程:

class A {
    A() {
        // super(); 
        System.out.println("class A");
    }
}
class B extends A {
    B() {
        // super(); 
        System.out.println("class B");
    }
}
class C extends B {
    public static void main(String args[]) {
        C c = new C(); //Parent constructor will get called
    }
}

当我创建类 C 的实例时,它调用超类的构造函数。那么,创建的对象不止一个吗?如果只创建一个对象,那么 super() 与另一个类的构造函数有何不同?super() 方法是否在内部创建一个对象?我所知道的是,构造函数也是一种方法(我可能错了)。

我的问题是:

  1. 在这种情况下创建了多少个 Object?
  2. 如果创建了一个对象,那么 Super() 如何在内部调用父类构造函数?
4

14 回答 14

64

好问题。您正在探索的是 Java 如何初始化对象- 并且涉及到许多步骤。

我知道构造函数也是一种方法(也许我错了)。

几乎正确。构造函数是一种特殊的方法。如果你反编译一个类文件,你会看到构造函数被重命名为<init>. <init>与其他方法不同,例如,不能显式调用,除非使用关键字newor super。这是非常基础的,以至于它是在 JVM 本身中实现的,而不是在 Java 语言中定义的东西。

在这种情况下创建了多少个 Object。

创建了一个对象 - 的一个实例C

C是另外且同时是 的一个实例B和 的一个实例A和 也Object.

如果创建了一个对象,那么在内部如何super()调用 Parent 类 Constructor 。Super 是如何调用父类构造函数的。

这是我们进入初始化的地方——初始化是JVM如何创建一个对象的新实例并设置所有成员值——特定类的成员值和超类的成员值。涉及几个阶段:

  • 加载所有引用的类并初始化这些类。类初始化本身并不重要,所以我不会在这里介绍它。非常值得一读。
  • 分配一块内存来保存实例的成员,其中将包括 和 的A所有B成员C注意,这解释了您问题的一个方面:基类及其子类的构造函数如何更新或引用同一个对象 -所有类的实例的所有成员一个接一个地存储在同一块内存中
  • 将所有成员初始化为其默认值。例如,intfloat成员将被设置为 0 和 0.0f。
  • 执行或计算成员初始化器,例如:

    private int a = 10;
    private int b = a * 5;
    private String c = Singleton.getInstance().getValue();
    
  • 注意 (1) 成员初始化严格按照在类中声明成员的顺序进行。这意味着声明中稍后对成员的引用被破坏:

    private int a = b * 5; // Forward reference; won't compile
    private int b = 10;
    
  • 注意 (2) 在 Java 中有一个未充分使用的工具来运行任意代码以在执行构造函数之前初始化值。这些代码块此时再次严格按照声明顺序执行:

    private int a;
    private int b = 1;
    {
        // Initization occurs after b but before c.
        // c cannot be referenced here at all
        int i = SomeClass.getSomeStatic();
        a = i * 2;
    }
    private int c = 99;
    
  • 执行 的构造函数C。构造函数必须直接从超类调用构造函数,否则编译器将自动添加super()为构造函数的第一行。这意味着构造函数严格按顺序执行:

    1. Object
    2. A
    3. B
    4. C

该对象现在已初始化并可以使用。如果您使用实例方法初始化值,您可能会做一些危险的事情:

public class Wrong {
    int a = getB(); // Don't do this!
    int b = 10;
    public int getB() {
         return b;
    }
}

在这里,a被初始化为0。这是因为,在getB()调用点时,Java 已将 的值清除为b默认值 ( 0),但尚未10在初始化的第二阶段将其设置为。

总之 - 只有一个对象,它是分阶段创建和初始化的。在这些阶段中,根据定义,对象并未完全定义。

于 2013-07-29T01:01:39.923 回答
8
  1. 将创建一个且只有一个对象,即。一个对象。

  2. 你可以想象当类 A 扩展 B 时,所有的方法和变量都被复制到类 A 中。

于 2013-07-26T09:37:22.757 回答
6

在代码中只会创建一个对象并超级调用父类构造函数。

对象创建的证明:

package one;

public class A {
    public static A super_var;

    public A() {
        super_var = this;
        System.out.println("Constrcutor of A invoked");
    }
}

package two;

public class B extends A {
    public static A sub_var;

    public B() {
        sub_var = this;
        System.out.println("Constructor of B invoked");
    }

    public void confirm() {
        if (sub_var == A.super_var)
            System.out.println("There is only one object is created");
        else
            System.out.println("There are more than one object created");
    }

    public static void main(String Args[]) {
        B x = new B();
        x.confirm();
    }
}

这将证明只会创建一个对象。

而关于Super(). 我知道它叫 Parent class constructor 。并且每个构造函数都Super()作为您在代码中提到的第一条语句。让你知道

我不知道它如何在内部调用超类构造函数。

希望这会让你明白只有你在程序中创建的实例

于 2013-07-26T09:25:33.610 回答
4
  1. 仅在您的情况下,正在创建 1 个对象。
  2. 调用子类构造函数时,会在内部调用超类的构造函数来初始化超类的成员。

调用构造函数并不意味着您正在创建对象。调用构造函数时已经创建了对象。对象首先由JVM创建(即在堆上分配内存,然后调用构造函数)。

构造函数用于初始化对象的成员。

于 2013-07-26T09:31:46.563 回答
2

您的课程将在内部转换为类似的内容

class A
{
    A(){
        super(); 
        System.out.println("class A");
    }
}

class B extends A{
    B(){
        super(); 
        System.out.println("class B");
    }
}

public class C extends B
{
    public static void main(String args[])
    {
        C c  = new C(); //Parent constructor will get call 
    }
}

在这种情况下创建了多少个 Object。

只有一个,即实例C,调用super()只是调用构造函数而不创建对象

如果创建了一个对象,那么 Super() 内部如何调用父类 Constructor 。Super 是如何调用父类构造函数的。

当你创建C的实例。C的构造函数被调用,它首先调用B的构造函数,然后再调用A的构造函数

于 2013-07-26T09:31:47.780 回答
2
How many number of Object is created in this case.

当您通过 C 类C cInstance = new C();的单个实例(对象)创建 C 类的实例时,将创建(A 和 B 都不是)。然而,由于 C 扩展 B 和 B 扩展 A,C 将具有 A 类和 B 类的所有方法(实际上取决于使用的访问修饰符,但在这种情况下可以说它们是公共的或默认的)。

If one object is created then how internally Super() is calling Parent class Constructor
. How Super is able to call parent class constructor.

这就是继承的工作原理。当创建一个新对象时,它将调用它的超类构造函数,而该超类将调用它的超类构造函数,依此类推。在其他普通函数中,您必须显式调用 super()。所以调用超类构造函数是自下而上的,而执行是继承层次树的自上而下的

于 2013-08-02T04:59:56.530 回答
1

我同意之前发布的答案,但想添加对这个问题的最终权威,Java 语言规范的引用。

该表达式new C()是“类实例创建表达式”。第15.9.4 节类实例创建表达式的运行时评估描述了创建对象所涉及的运行时步骤。请注意,它指的是“对象”,并且只分配一次空间,但声明“接下来,调用指定类类型的选定构造函数。这导致为类类型的每个超类调用至少一个构造函数。”

通过区分创建新对象和调用构造函数,这一切变得更加清晰。调用构造函数仅完成对象创建的一部分、运行初始化程序、超类构造函数和构造函数主体的部分。因为 C 也是 B,所以 B 构造函数必须在创建 C 期间运行。

于 2013-07-31T04:21:27.343 回答
1

我不确定多态性/覆盖在 GC 时是如何工作的。

但是应该值得尝试覆盖finalize所有类中的方法并检查 JVM 何时退出 main 方法。

  • 如果只C创建对象,它应该调用finalize“C”。
  • 如果创建了所有A, B,对象,C它应该调用finalize, A, 。BC

我认为这是您可以申请的最简单的检查。

class A {
    A() {
        //Super(); 
        System.out.println("class A");
    }

    public void finalize(){
    System.out.println("Class A object destroyed");
    }
}
class B extends A {
    B() {
       //Super(); 
        System.out.println("class B");
    }

    public void finalize(){
    System.out.println("Class B object destroyed");
    }
}
class C extends B {
    public static void main(String args[]) {
        C c = new C(); //Parent constructor will get call 
    }

    public void finalize(){
    System.out.println("Class C object destroyed");
    } 
}
于 2013-07-30T14:03:38.840 回答
1

如果您按照SO 答案查看对象分配的动态,则必须清楚使用new运算符,每个语句只创建一个对象。为了进一步澄清只有一个对象被创建的疑问,通过这个程序:

public class A {
    public static int aInstanceCount=0;
    public static A aInstance;
    public String aInstanceVariable;
    A() {
//Super();
        aInstanceCount++;
        aInstanceVariable="aInstanceVar";
        System.out.println("class A");
        aInstance=this;
    }
}

class B extends A {
    public static int bInstanceCount=0;
    public static B bInstance;
    public String bInstanceVariable;
    B() {
//Super();
        bInstanceCount++;
        bInstanceVariable="bInstanceVar";
        System.out.println("class B");
        bInstance=this;
    }
}

class C extends B {
    public static void main(String args[]) {
        int instanceCount=0;
        C c = new C(); //Parent constructor will get call
        if(A.aInstance!=null){
            instanceCount++;
            System.out.println("Value of aInstanceVariable: "+A.aInstance.aInstanceVariable);

        }
        if(B.bInstance!=null){
            instanceCount++;
            System.out.println("Value of bInstanceVariable: "+B.bInstance.bInstanceVariable);
        }
        A a=A.aInstance;
        B b=B.bInstance;
        System.out.println("bInstanceVariable of B earlier: " + B.bInstance.bInstanceVariable);
        //Now we are changing the bInstanceVariable of c which is inherited from B
        c.bInstanceVariable="bInstance After modified by C";
        System.out.println("bInstanceVariable of B after: " + B.bInstance.bInstanceVariable);
        System.out.println("aInstanceVariable of A earlier: " + A.aInstance.aInstanceVariable);
        //Now we are changing the aInstanceVariable of c which is inherited from A
        c.aInstanceVariable="aInstance After modified by C";
        System.out.println("bInstanceVariable of A after: " + A.aInstance.aInstanceVariable);
    }
}

输出:

class A
class B
Value of aInstanceVariable: aInstanceVar
Value of bInstanceVariable: bInstanceVar
bInstanceVariable of B earlier: bInstanceVar
bInstanceVariable of B after: bInstance After modified by C
aInstanceVariable of A earlier: aInstanceVar
bInstanceVariable of A after: aInstance After modified by C

如果你能注意到,每次创建子类对象时都会隐式调用超级构造函数,但由于new运算符只使用一次,因此只有一个对象实际分配了空间。而通过修改aInstanceVariableviaC对象c,我们实际上是在改变aInstanceVariableof aInstance。所以它清楚地证明了实际上存在一个对象。

于 2013-07-26T10:10:02.543 回答
1

调用构造函数创建对象时创建对象的步骤:

  1. 使用init完成内存分配。这个init进行系统调用来为对象创建分配内存。

  2. 然后调用您的构造函数来初始化对象的字段。

  3. 然后它调用超类构造函数(如果有任何超类)并重复步骤 1 到 3。

使用javap反编译类文件时所看到的内容显示了要进行的不同调用。init进行系统调用以初始化内存分配,但在运行构造函数的代码时会初始化对象的字段。

于 2013-07-29T07:27:05.133 回答
1
  1. 在您的情况下,创建了一个对象

  2. 在执行以下操作时,此 super() 将由编译器隐式提供

    class A {
    A() {
        System.out.println("class A");
    }
    }
    class B extends A {
    B() {
        System.out.println("class B");
    }
    }
    class C extends B {
    public static void main(String args[]) {
        C c = new C(); //
    }
    }
    

它类似于在您的方法中调用 super()

    B() {
        super();
        System.out.println("class B");
    }

当当前类中的方法被覆盖,但您想调用超类方法时,也可以使用 super 关键字。

super() 将使所有构造函数都引用一个类。(为了便于理解:它就像所有的成员函数都属于同一个类。)它只会调用所有的构造函数方法。

所以它只完成了调用构造函数的工作,所以 super() 不会创建任何对象。它只是指成员函数。

于 2013-08-01T09:50:40.887 回答
1

super 关键字使子类能够调用其超类的方法和字段。它不是超类对象的实例,而是一种告诉编译器要引用哪些方法或字段的方法。效果与子类调用其自己的方法之一相同。 例子:

考虑一个扩展其超类 Person 的子类 Employee:

public class Employee extends Person{

   public Employee()
   {
     //reference the superclass constructor 
     super(); 
   }

   public String getName()
   {
     //reference superclass behaviors
     return super.getFirstName() + " " + super.getLastName();
   }
 } 

super 关键字可用于引用 Person 类的构造函数或它有权访问的任何行为或字段(例如,getFirstName() 和 getLastName())。

于 2013-08-01T06:21:14.940 回答
1

如果您再添加一行代码System.out.println(this.hashCode())将消除您的困惑。

Here in All CasehashCode()将始终打印相同hashCode的内容。这意味着只有一个独特Object的被创造出来。

class A {
    A() {
        // super(); 
        System.out.println(this.hashCode()); // it will print 2430287
        System.out.println("class A");
    }
}
class B extends A {
    B() {
        // super(); 
        System.out.println(this.hashCode()); // it will print 2430287
        System.out.println("class B");
    }
}
class C extends B {
    public static void main(String args[]) {
        C c = new C(); //Parent constructor will get called
        System.out.println(this.hashCode()); // it will print 2430287
    }
}

但是有两个构造函数被调用来初始化Parent成员变量。我想如果您知道super()调用类的构造函数parent并初始化类的成员变量的关键字的概念parent

于 2016-04-09T05:14:58.840 回答
0

3个构造函数将调用

代码:

class A
{
    A()
    {
        System.out.println("In A");
    }
}

class B extends A
{
    B()
    {
        System.out.println("In B");
    }
}

class C extends B
{
    C()
    {
        System.out.println("In C");
    }
}

public class InheritanceTest {
    public static void main(String args[])



    {
        C c1=new C();
    }

}

输出:

在一个

在乙

在 C 中

于 2018-01-29T12:20:05.487 回答