可以用构造器来进行初始化。在运行时刻,可以调用方法或执行某些动作来确定初值,这为编程带来了更大的灵活性。但要牢记:[b]无法阻止自动初始化的进行,它将在构造器被调用之前发生[/b]。例如下述代码:

public class Counter{
	int i;
	Counter(){ i = 7; }
	//......
}

那么i首先会被置为0,然后变为7。对于所有基本类型和对象引用,包括在定义时已经指定初值的变量,这种情况都成立;[b]因此,编译器不会强制你一定要在构造方法的某个地方或在使用它们之前对元素进行初始化——因为初始化早已得到了保证[/b]。

[size=medium]1、初始化顺序[/size]

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

class Window{
	Window(int marker){System.out.println("Window("+marker+")");}
}

class House{
	Window w1 = new Window(1);
	House(){
		System.out.println("House()");
		w3 = new Window(33);
	}
	Window w2 = new Window(2);
	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();
	}
}

程序运行结果如下:

[img]http://dl.iteye.com/upload/attachment/332862/2d6eb837-0255-39a0-9f0f-5d4279f5c953.jpg[/img]

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

由输出可见,w3这个引用会被初始化两次:一次在调用构造器前,一次在调用期间(第一次引用的对象将被丢弃,并作为垃圾回收)。试想,如果定义了一个重载构造器,它没有初始化w3,;同时在w3的定义里也没有指定默认值,那会产生什么后果呢?所以尽管这种方法似乎效率不高,但它的确能使初始化得到保证。

[size=medium]2、静态数据的初始化[/size]

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

[/b]

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

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

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

程序运行结果如下:

[img]http://dl.iteye.com/upload/attachment/332841/9c4f3113-b92d-3e81-83e8-3c03775e6f28.jpg[/img]

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

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

初始化的顺序是[b]先静态对象[/b](如果它们尚未因前面的对象创建过程而被初始化),而[b]后是“非静态”对象[/b]。从输出结果中可以观察到这一点。要执行main()(静态方法),必须加载StaticInitialization类,然后其静态域table和cupboard被初始化,这将导致它们对应的类也被加载,并且由于它们也都包含静态的Bowl对象,因此Bowl随后也被加载。这样,在这个特殊的程序中的所有类在main()开始之前就都被加载了。实际情况通常并非如此,因为在典型的程序中,不会像在本例中所做的那样,将所有的事物都通过static联系起来。

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

(1)即使没有显式地使用static关键字,构造器实际上也是静态方法。因此,当首次创建类型为Dog的对象时(构造器可以看成静态方法),或者Dog类的静态方法/静态域首次被访问时,Java解释器必须查找类的路径,以定位Dog.class文件。

(2)然后载入Dog.class,有关静态初始化的所有动作都会执行。因此,[b]静态初始化只在Class对象首次加载的时候进行一次[/b]。

(3)当用new Dog()创建对象的时候,首先将在堆上为Dog对象分配足够的存储空间。

(4)这块存储空间会被清零,这就自动地将Dog对象中的所有基本数据类型数据都设置成了默认值(对数字来说就是0,对于boolean和char也相同),而引用则被设置成了null。

(5)执行所有出现于字段定义处的初始化动作。

(6)执行构造器。

[size=medium]3、显式的静态初始化[/size]

Java允许将多个静态初始化动作组织成一个特殊的“静态句子”(有时也叫做“静态块”)。就像下面这样:

public class Spoon{
	static int i;
	static{
		i =7;
	}
}

尽管上面的代码看起来像一个方法,但实际只是一段跟在static关键字后面的代码。与其他静态初始化动作一样,[b]这段代码仅执行一次:当首次生成这个类的一个对象时,或者首次访问属于那个类的静态数据成员时(即便从未生成过那个类的对象)[/b]。例如:

class Cup{
	Cup(int marker){
		System.out.println("Cup("+marker+")");
	}
	void f(int marker){
		System.out.println("f("+marker+")");
	}
}
class Cups{
	static Cup cup1;
	static Cup cup2;
	static{
		cup1 = new Cup(1);
		cup2 = new Cup(2);
	}
	Cups(){
		System.out.println("Cups()");
	}
}
public class ExplicitStatic{
	public static void main(String args[]){
		System.out.println("Inside main()");
		Cups.cup1.f(99);//(1)
	}
	//static Cups cups1 = new Cups();//(2)
	//static Cups cups2 = new Cups();//(2)
}

运行结果1(注释掉(2)):

[img]http://dl.iteye.com/upload/attachment/332843/bba9a0fc-0828-3d7d-8a3b-9211b95fcedc.jpg[/img]

运行结果2(注释掉(1)):

[img]http://dl.iteye.com/upload/attachment/332845/4974dc8d-c18a-319e-a423-2d8aa567fe5f.jpg[/img]

运行结果3(注释掉(1)和(2)):

[img]http://dl.iteye.com/upload/attachment/332847/c9293534-a8e4-3665-ac87-705965e3837f.jpg[/img]

运行结果4((1)和(2)均不注释掉):

[img]http://dl.iteye.com/upload/attachment/332849/bcf85018-105b-3490-8389-48e6605d85ab.jpg[/img]

从上述结果可以看出:无论是通过标为(1)的那行代码访问静态的cup1对象,还是把标为(1)的行注释掉,让它去运行标为(2)的那行代码,Cups的静态初始化动作都会得到执行。如果把标为(1)和(2)的行同时注释掉,Cups静态初始化动作就不会进行,就像在输出中看到的那样。此外,激活一行还是两行标为(2)的代码都无关紧要,[b]静态初始化动作只进行一次[/b]。

[size=medium]4、非静态实例初始化[/size]

Java中也有被称为实例初始化的类似语法,用来初始化每一个对象的非静态变量。例如:

class Mug{
	Mug(int marker){
		System.out.println("Mug("+marker+")");
	}
	void f(int marker){
		System.out.println("f("+marker+")");
	}
}
public class Mugs{
	Mug mug1;
	Mug mug2;
	{
		mug1 = new Mug(1);
		mug2 = new Mug(2);
		System.out.println("mug1&mug2 initialized");
	}
	Mugs(){
		System.out.println("Mugs()");
	}
	Mugs(int i){
		System.out.println("Mugs(int)");
	}
	public static void main(String args[]){
		System.out.println("Inside main()");
		new Mugs();
		System.out.println("new Mugs() completed");
		new Mugs(1);
		System.out.println("new Mugs(1) completed");
	}
}

程序运行的结果如下:

[img]http://dl.iteye.com/upload/attachment/332851/82426ace-c4d3-3979-a6e7-e7acb5ed09c0.jpg[/img]

你可以看到实例初始化子句:

{
	mug1 = new Mug(1);
	mug2 = new Mug(2);
	System.out.println("mug1&mug2 initialized");
}

看起来它与静态初始化子句一模一样,只不过少了static关键字。这种语法对于支持“匿名内部类”的初始化是必须的,但是它也使得你可以保证无论调用了哪个显式构造器,某些操作都会发生。从输出中可以看到实例初始化子句是在两个构造器之前执行的。