13

When does the instance variable get initialized? Is it after the constructor block is done or before it?

Consider this example:

public abstract class Parent {

 public Parent(){
   System.out.println("Parent Constructor");
   init();
 }

 public void init(){
   System.out.println("parent Init()");
 }
}

public class Child extends Parent {

private Integer attribute1;
private Integer attribute2 = null;

public Child(){
    super();
    System.out.println("Child Constructor");
}

public void init(){
    System.out.println("Child init()");
    super.init();
    attribute1 = new Integer(100);
    attribute2 = new Integer(200);
}

public void print(){
    System.out.println("attribute 1 : " +attribute1);
    System.out.println("attribute 2 : " +attribute2);
}
}

public class Tester {

public static void main(String[] args) {
    Parent c = new Child();
    ((Child)c).print();
    
}
}

OUTPUT:

Parent Constructor

Child init()

parent Init()

Child Constructor

attribute 1 : 100

attribute 2 : null


  1. When are the memory for the atribute 1 & 2 allocated in the heap ?

  2. Curious to know why is attribute 2 is NULL ?

  3. Are there any design flaws?

4

3 回答 3

13

何时在堆中分配属性 1 和 2 的内存?

new在调用运算符时,在java.lang.Object进入构造函数之前,为整个对象分配内存。内存是为 中的单个Integer实例分配的init,但是没有必要为单个属性分配内存——只有整个对象。

想知道为什么属性 2 是 NULL ?

init方法在超级构造函数中被调用,因此attribute2被赋值new Integer(200),然后子类构造函数被调用,它按照属性初始值设定项在源代码中出现的顺序应用它们。这条线

private Integer attribute2 = null;

覆盖由init()to分配的值null

如果您将呼叫添加到

 System.out.println("attribute 2 : " +attribute2);

在您致电super();之后,这将变得显而易见。

有没有设计缺陷?

在基类完成初始化之前调用子类方法是危险的。子类可能依赖其基类的不变量来保护自己的不变量,如果基类构造函数尚未完成,则其不变量可能不成立。

这也可能使 C++ 程序员等感到困惑,他们希望init从基类调用来调用基类的版本,因为 C++ 在输入构造函数时重写了 vtable 指针。

有关所有血腥细节,请参阅Java 语言规范

于 2012-09-06T21:35:54.063 回答
2

使用此处提供的答案和链接后,我的摘要观察结果如下:


这是流程:

  1. 输入子类构造函数。孩子(){ ... }

  2. 调用显式 super() [调用父类构造函数]。

  3. 输入 Parent() { ... } 类构造函数

  4. 调用隐式 super() [调用 Object 类构造函数]

  5. 输入 Object(){ } (没有超级构造函数调用)

  6. 对超类构造函数的递归调用到此结束。

  7. 返回 Object 类构造函数

  8. 现在在父类构造函数中......父类的实例初始化器和实例变量初始化器被执行。

  9. 父类构造函数的其余部分被执行并返回

  10. 现在在 Child 类构造函数中。Child 类的实例初始化器和实例变量初始化器被执行。

  11. 然后执行 Child 类构造函数的其余部分并完成对象初始化过程。


原因属性 2 为 NULL,因为

  1. 在第 9 步为属性 2 分配了一个值 200。
  2. 但在步骤 10 中被覆盖为 NULL

有没有设计缺陷?

正如 Fabian Barney 提到的 ::::: 在构造函数中调用可以被子类覆盖的方法通常是不好的做法。

何时在堆中分配属性 1 和 2 的内存? 还在摸索中。感谢任何指针。

感谢迈克和费边

于 2012-09-06T22:37:14.990 回答
1

请参阅下面说明初始化流程的代码及其控制台输出。请注意,Parent 类的“在构造对象时”方法和属性根本不会被使用(调用)。

代码

class Parent{
   static{ System.out.println("Static Parent initialization"); }

   int i=1234;
   {  System.out.println("Parent's instance initializer running");
      print("executed from Parent's instance initializer");
      update(1);
   }

   Parent() {
      System.out.println("Parent's constructor running");
      print("executed from Parent's constructor");
      update(2);
   }

   void print(String note) { System.out.println("never executed"); }
   void update(int newI){ System.out.println("never executed"); }
}

class Child extends Parent{
   static{ System.out.println("Static Child initialization"); }

   int i = 3;
   {System.out.println("Child's instance initializer; i=" + i); }

   Child(){super(); i=4; System.out.println("Child's constructor running. Setting i to 4");}

   void print(String note) { System.out.println("print(): '"+note.toUpperCase()+"' ; i="+i); }
   void update(int newI){i=newI;System.out.println("update("+newI+"): After update i="+i);}
}

class Context {
   public static void main(String[] args) {
      Parent parent = new Child();
      System.out.println("In main: child's i="+((Child)parent).i);
      System.out.println("In main: parent's i=" +parent.i);
   }
}

输出

静态父初始化

静态子初始化

父实例初始化程序运行

print(): '从父实例初始化器中执行' ; 我=0

更新(1):更新后 i=1

父母的构造函数运行

print(): '从父构造函数中执行' ; 我=1

更新(2):更新后 i=2

Child的实例初始化器;我=3

孩子的构造函数正在运行。将 i 设置为 4

主要:孩子的 i=4

主要是:父母的 i=1234

于 2021-06-17T12:11:16.717 回答