何时初始化实例变量并分配值?
当doees实例变量初始化? 是在构造函数块完成之后还是之前?
考虑这个例子:
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:
父构造函数
子init()
父Init()
儿童构造函数
属性1:100
属性2:null
-
当在堆中分配属性1和2的内存时?
-
很想知道为什么属性2是NULL?
-
有任何设计缺陷吗?
当在堆中分配属性1和2的内存时?
在输入java.lang.Object
构造函数之前,在调用new
运算符时分配整个对象的内存。 在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指针。
有关所有血腥细节,请参阅Java语言规范 。
在消费了答案后,这里提供的链接是我的摘要观察:
这是流程:
-
输入Child类构造函数。 孩子(){…}
-
调用显式的super()[调用父类构造函数]。
-
输入Parent(){…}类构造函数
-
调用隐式super()[调用Object类构造函数]
-
输入Object(){}(没有超级构造函数调用)
-
超类构造函数的递归调用在此结束。
-
Object类构造函数的返回值
-
现在在Parent类构造函数中……实现了Parent类的实例初始值设定项和实例变量初始值设定项。
-
执行其余的父类构造函数并返回
-
现在在Child类构造函数中。 实现了Child类的实例初始值设定项和实例变量初始值设定项。
-
然后执行其余的Child类构造函数并完成对象初始化过程。
attribute2的原因是NULL,因为
- attribute2被赋值为200 @ step 9。
- 但在步骤10中重写为NULL
有任何设计缺陷吗?
正如Fabian Barney提到:::::在构造函数中调用可以被子类覆盖的方法通常是不好的做法。
当在堆中分配属性1和2的内存时? 还在搞清楚。 感谢任何指针。
感谢Mike和Fabian