Java 初始化顺序


1、初始化顺序


    在类的内部,变量定义的先后顺序决定了初始化的顺序。即使变量定义散布于方法定义之间,它们仍旧会在任何方法(包括构造器)被调用之前得到初始化。例如:

class Window {
	Window(int marker) {
		System.out.println("Window(" + marker+ ")");
	}
}
class House {
	Window w1 = new Window(1); // before constructor
	
	House() {
		System.out.println("House()");
		w2 = new Window(33);
	}
	
	Window w2 = new Window(2); // after constructor
	
	void f() {
		System.out.println("f()");
	}
	
	Window w3 = new Window(3);
}
public class OrderOfInitialization {
	public static void main(String[] args) {
		House h = new House();
		h.f();
	}
}

结果为:

Window(1)
Window(2)
Window(3)
House()
Window(33)
f()

在House类中,故意把几个Window对象的定义散布到各处,以证明它们全部会在调用构造器或者其它方法之前得到初始化。

此外,w3在构造器内再次被初始化。


由输出可见,w3这个引用会被初始化两次:一次在调用构造器前,一次在调用期间(第一次引用的对象将被丢弃,并作为垃圾回收)。


2、静态数据初始化



    无论创建多少个对象,静态数据都只占用一份存储区域。static关键字不能应用于局部变量,因此它只能作用于域。如果一个域是静态的基本类型域,且也没有对它进行初始化,那么它就会获得基本类型的标准初值;如果它是一个对象引用,那么它的默认初始化值就是null。



    如果想在定义处进行初始化,采取的方法与非静态数据没什么不同。



    要想了解静态存储区域是何时初始化的,就请看下面这个例子:



class Bowl {
	Bowl(int marker) {
		System.out.println("Bowl(" + marker + ")");
	}
	
	void f1(int marker) {
		System.out.println("f1(" + marker + ")");
	}
}
class Table {
	static Bowl bowl1 = new Bowl(1);
	
	Table() {
		System.out.println("Table()");
		bowl1.f1(1);
	}
	
	void f2(int marker) {
		System.out.println("f2(" + marker + ")");
	}
	
	static Bowl bowl2 = new Bowl(2);
}
class Cupboard {
	Bowl bowl3 = new Bowl(3);
	static Bowl bowl4 = new Bowl(4);
	Cupboard() {
		System.out.println("Cupboard()");
		bowl4.f1(2);
	}
	
	void f3(int marker) {
		System.out.println("f3(" + marker + ")");
	}
	
	static Bowl bowl5 = new Bowl(5);
}
public class StaticInitialization {
	public static void main(String[] args) {
		System.out.println("Creating new Cupboard() in main");
		new Cupboard();
		System.out.println("Creating new Cupboard() in main");
		new Cupboard();
		table.f2(1);
		cupboard.f3(1);
	}
	static Table table = new Table();
	static Cupboard cupboard = new Cupboard();
}


结果为:


Bowl(1)
Bowl(2)
Table()
f1(1)
Bowl(4)
Bowl(5)
Bowl(3)
Cupboard()
f1(2)
Creating new Cupboard() in main
Bowl(3)
Cupboard()
f1(2)
Creating new Cupboard() in main
Bowl(3)
Cupboard()
f1(2)
f2(1)
f3(1)


    Bowl类使得看到类的创建,而Table类和Cupboard类在它们的类定义中加入了Bowl类型的静态数据成员。注意,在静态数据成员定义之前,Cupboard类先定义了一个Bowl类型的非静态数据成员b3。


    由输出可见,静态初始化只有在必要时刻才会进行。如果不创建Table对象,也不引用Table.b1或Table.b2,那么静态的Bowl b1和b2永远都不会被创建。只有在第一个Table对象被创建(或者第一次访问静态数据)的时候,它们才会被初始化。此后,静态对象不会再次被初始化。



初始化的顺序是先静态对象(如果它们尚未因前面的对象创建过程而被初始化),而后是"非静态"对象。从输出结果中可以观察到这一点。



要执行main()(静态方法),必须加载StaticInitialization类,然后其静态域table和cupboard被初始化,这将导致它们对应的类也被加载,



并且由于它们也都包含静态的Bowl对象,因此Bowl随后也被加载。这样,在这个特殊的程序中的所有类在main()开始之前就都被加载了。



总结一下对象的创建过程,假设有个名为Dog的类:



  1. 即使没有显式地使用static关键字,构造器实际上也是静态方法。因此,当首次创建类型为Dog的对象时(构造器可以看成静态方法),或者Dog类的静态方法/静态域首次被访问时,Java解释器必须查找类路径,以定位Dog.class文件。
  2. 然后载入Dog.class,有关静态初始化的所有动作都会执行。因此,静态初始化只在Class对象首次加载的时候进行一次。
  3. 当用new Dog()创建对象的时候,首先将在堆上为Dog对象分配足够的存储空间。
  4. 这块存储空间会被清零,这就自动地将Dog对象中的所有基本类型数据都设置成了默认值(对数字来说就是0,对布尔型和字符型也相同),而引用则被设置成了nul。
  5. 执行所有出现于字段定义处的初始化动作。
  6. 执行构造器。

3、继承初始化


    了解包括继承在内的初始化全过程,以对所发生的一切有个全局性的把握,是很有益的。请看下例:


class Insect {
	private int i = 9;
	protected int j;
	
	Insect() {
		System.out.println("i = " + i + ", j = " + j);
		j = 39;
	}
	
	private static int x1 = printInt("static Insect.x1 initialized");
			
	static int printInt(String s) {
		System.out.println(s);
		return 47;
	}
}
public class Beetle extends Insect{
	private int k = printInt("Beetle.k initialized");
	private static int x2 = printInt("static Beetle.x2 initialized");
	public Beetle() {
		System.out.println("k = " + k);
		System.out.println("j = " + j);
 	}
	public static void main(String[] args) {
		System.out.println("Beetle constructor");
		Beetle beetle =  new Beetle();
	}
}


     结果为:   


static Insect.x1 initialized
static Beetle.x2 initialized
Beetle constructor
i = 9, j = 0
Beetle.k initialized
k = 47
j = 39



     在Beetle上运行Java时,所发生的第一件事情就是试图访问Beetle.main()(一个static方法),于是加载器开始启动并找出Beetle类的编译代码(在名为Beetle.class的文件之中)。在对它进行加载的过程中,编译器注意到它有一个基类(这是由关键字extends得知的),于是它继续进行加载。不管你是否打算产生一个该基类的对象,这都要发生。






    至此为止,必要的类都已加载完毕,对象就可以被创建了。首先,对象中所有的基本类型都会被设为默认值,对象引用被设为null--这是通过将对象内存设为二进制零值而一举生成的。然后,基类的构造器会被调用。在本例中,它是被自动调用的。但也可以用super来指定对基类构造器的调用。基类构造器和导出类的构造器一样,以相同的顺序来经历相同的过程。在基类构造器完成之后,实例变量按其次序被初始化。最后,构造器的其余部分被执行。