无论创建多少个对象,静态数据都只占用一份存储区域。static关键字不能应用于局部变量,因此它只能作用于域。如果一个域是静态的基本类型域,且也没有对它进行初始化,那么它就会获得基本类型的标准初值;如果它是一个对象引用,那么它的默认初始化值就是null。
如果想在定义处进行初始化,采用的方法与非静态数据没什么不同。要想了解静态存储区域是何时初始化的,就请看下面这个例子:
class Bowl{
Bowl(int marker){
System.out.print("Bowl("+marker+")");
}
void f1(int marker){
System.out.print("f1("+marker+")");
}
}
class Table{
static Bowl bowl1=new Bowl(1);
Table(){
System.out.pring("Table()");
bowl2.f1(1);
}
void f2(int marker){
System.out.print("f2("+marker+")");
}
static Bowl bowl2=new Bowl(2);
}
class Cupboard{
Bowl bowl3=new Bowl(3);
Cupboard(){
System.out.pring("Cupboard()");
bowl4.f1(2);
}
void f3(int marker){
System.out.print("f3("+marker+")");
}
static Bowl bowl5=new Bowl(5);
}
public class SraticInitialization{
public static void main(String[]args){
System.out.print("Creating new Cupboard()in main");
new Cupboard();
System.out.print("Creating new Cupboard()in main");
new Cupboard();
table.f2(1);
cupboard.f3(1);
}
static Table table=new Table();
static 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()开始之前就都被加载了。实际情况通常并非如此,因为在典型的程序中,不会像在本例中所做的那样,将所有的事物都通过static联系起来。
总结一下对象的创建过程,假设有个名为Dog的类:
1、即使没有显示地使用static关键字,构造器实际上也是静态方法。因此,当首次创建类型为Dog的对象时(构造器可以看成静态方法),或者Dog类的静态方法/静态域首次被访问时,Java解释器必须查找类路径,以定位Dog.class文件。
2、然后载入Dog.class(这将创建一个Class对象),有关静态初始化的所有动作都会执行。因此,静态初始化只在Class对象首次加载的时候进行一次。
3、当用new Dog()创建对象的时候,首先将在堆上为Dog对象分配足够的存储空间。
4、这块存储空间会被清零,这就自动地将Dog对象中的所有基本类型数据都设置成了默认值(对数字来说就是0,对布尔型和字符型也相同),而引用则被设置成了null。
5、执行所有出现于字段定义处的初始化动作。
6、执行构造器。这可能会牵涉到很多动作,尤其是涉及继承的时候。
|