【问题标题】:When do instance variables get initialized and values assigned?实例变量何时初始化并赋值?
【发布时间】:2012-08-31 18:44:05
【问题描述】:

实例变量什么时候初始化?是在构造函数块完成之后还是之前?

考虑这个例子:

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();
    
}
}

输出:

父构造函数

子初始化()

父初始化()

子构造函数

属性 1:100

属性 2:空


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

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

  3. 是否存在设计缺陷?

【问题讨论】:

  • 顺便说一句,实例变量的 Java 术语是“字段”。

标签: java object inheritance abstract-class instance


【解决方案1】:

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

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

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

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

private Integer attribute2 = null;

init()分配的值覆盖到null

如果你添加一个调用

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

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

是否存在设计缺陷?

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

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

有关所有血腥细节,请参阅The Java Language Specification

【讨论】:

  • 感谢 Mike,设计:我的意思是让子 init() 在其自己的构造函数之前执行是一个好习惯。因为在这里它违背了我的孩子 init() 的目的。您对此有何看法?
  • 另外,将实例变量初始化为 null 是否是一种好习惯?
  • @AKh 在构造函数中调用可以被子类覆盖的方法通常是不好的做法。您不希望您的子类能够破坏您的类。
  • @AKh,请查看我的编辑。在null 上,如果您可以避免变量成为null,那么您已经消除了程序失败的一种方式。例如,使用对空数组的引用而不是 null 引用可以简化代码。也就是说,有时null 是正确的值。如果映射不包含某个键的条目,那么 Java 中使用的约定是 get 为该键返回 null。
【解决方案2】:

在阅读完这里提供的答案和链接后,这是我的摘要观察:


流程如下:

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

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

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

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

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

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

  7. Object 类构造函数的返回

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

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

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

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


原因属性 2 为 NULL,因为

  1. attribute2 在第 9 步被赋值为 200。
  2. 但在步骤 10 中被覆盖为 NULL

是否存在设计缺陷?

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

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

感谢 Mike 和 Fabian

【讨论】:

    【解决方案3】:

    请参阅下面说明初始化流程的代码及其控制台输出。请注意,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

    update(1):更新后 i=1

    父的构造函数运行

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

    update(2):更新后 i=2

    孩子的实例初始化器; i=3

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

    主要:孩子的 i=4

    主要:父母的 i=1234

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-07-04
      • 2017-04-05
      • 1970-01-01
      • 2011-03-18
      • 2010-11-26
      • 1970-01-01
      • 1970-01-01
      • 2014-09-14
      相关资源
      最近更新 更多