Java中的构造函数与字段初始化顺序

在java中我们通过new ClassName()来创建对象,那么创建对象的整个过程是怎么样的呢?其实,在创建对象时, 首先虚拟机会为对象的所有字段分配内存, 包括哪些从父类继承来的字段, 而且会把这些字段初始化为它们各自类型的默认值, 比如数值类型的默认值为0, boolean类型的为false, char类型的为"\u0000", 引用类型为null。在此之后, 构造过程分为三个阶段 : 

1、调用父类的构造函数

2、用这些字段的初始器和初始化块来初始化它们;

3、执行构造函数体

其中,首先执行的时显式或者隐式的调用父类的构造函数, 如果使用了显式的调用this构造函数,那么这样的调用链将会链接下去,知道找到一个隐式或者显式的父类构造函数调用为止,然后调用该父类的构造函数。 父类的构造函数的执行过程也分为同样的三个阶段, 知道整个过程达到Object类的构造函数为止。任何作为显示构造函数调用的组成部分进行计算的对象都不允许引用当前对象的任何成员。

在第二阶段, 所有的字段初始化器和初始化块都是按照他们的生命顺序来执行的。这个阶段,允许引用当前对象的其他成员, 只要他们已经被声明过即可。

最后,执行构造函数体中的实际语句。如果该构造函数是被显式的调用,那么一旦其执行结束,控制流就会返回调用它的构造函数, 执行那个构造函数的剩余部分。这个过程将会不断地重复,直到在new构造语句中所使用的构造函数体都已经执行完成之后才停止。

如果在构造过程中抛出异常,那么new表达式将会在抛出那个异常的同时终止,此时不会返回任何新对象的引用。

下面是一个例子, 可以追中说明构造过程的不同阶段 :

package com.thingking.in.java;

// class x
public class X {
	protected int xMask = 0x00ff;
	protected int fullMask;

	public X() {
		fullMask = xMask;
	}

	public int mask(int origin) {
		return (origin & fullMask);
	}

}

// class y
class Y extends X {

	protected int yMask = 0xff00;

	public Y() {
		fullMask |= yMask;
	}

}
如果创建了一个ClassY的对象, 就可以逐步地进行构造, 那么就可以得到每一步的字段对应的值。

Java中的构造函数与字段初始化顺序_第1张图片

在第五步中,如果X的构造函数调用了mask方法,那么它使用的fullmask值将是0x00ff, 而不是0xffff。事实确实如此, 尽管在对象完全构造之后,对mask的调用所使用的fullmask值是0xffff。

而且, 假设类Y的实现覆盖了mask方法,且在计算中显式地使用了yMask字段。如果X的构造函数中使用的是mask方法,那么实际上它想调用的将是Y的mask方法,而此时yMask的值将是0而不是想要的0xff00。

在设计对象构造阶段调用的这些方法时,必须考虑以上这些因素。构造函数应该避免调用哪些可覆盖的方法, 即不是私有、静态的活着final的方法。如果确实调用了这样的方法,那么必须在文档中将它们清晰地列出来,以提醒所有想要用潜在的非常规的使用方式来覆盖这些方法的人。

你可能感兴趣的:(JavaSE)