
4.5 对象初始化和清除
在Java程序中需要创建许多对象,为对象确定初始状态称为对象初始化。对象初始化主要是指初始化对象的成员变量。实例变量和静态变量的初始化略有不同。当一个对象不再使用,应该清除以释放它所占的空间,通过垃圾回收器清除对象。
4.5.1 实例变量的初始化
Java语言能够保证所有的对象都被初始化。实例变量的初始化方式有声明时初始化、使用初始化块和使用构造方法初始化。
1.成员变量默认值
在类的定义中如果没有为变量赋初值,则编译器为每个成员变量指定一个默认值。对引用类型的变量,默认值为null。各种类型数据的初始值如表4-1所示。
表4-1 各种类型数据的初始值

下面程序演示了几个变量的默认值。
程序4.9 Student.java

程序运行结果为:

输出结果表明,在类的定义中没有为成员变量指定任何值,但在创建对象后,每个成员变量都有了初值,初值是该类型的默认值。这些变量的初值是在调用默认构造方法之前获得的。
注意:对于方法或代码块中声明的变量,编译器不为其赋初始值,使用之前必须为其赋初值。
2.在变量声明时初始化
可以在成员变量声明的同时为变量初始化,如下所示。

还可以使用方法为变量初始化。例如:

其中,f()为该类定义的方法,返回一个double型值,然后用该值为marks初始化。
3.使用初始化块初始化
在类体中使用一对大括号定义一个初始化块,在该块中可以对实例变量初始化。例如:

注意:初始化块是在调用构造方法之前调用的。
4.使用构造方法初始化
可以在构造方法中对变量初始化。例如,对于Student类可以定义下面的构造方法。

使用构造方法对变量初始化可以在创建对象时执行初始化动作。成员变量id、name、marks、pass等先执行自动初始化,即先初始化成默认值,然后才赋予指定的值。
5.初始化次序
如果在类中既为实例变量指定了初值,又有初始化块,还在构造方法中初始化了变量,那么它们执行的顺序如何?最后变量的值是多少?下面的程序说明了初始化的顺序。
程序4.10 InitDemo.java

程序运行结果为:

从上面程序输出结果可以看到,构造方法被最后执行。实际上,程序是按下面顺序为实例变量x初始化的。
(1)首先使用默认值或指定的初值初始化,这里先将x赋值为100。
(2)接下来执行初始化块,重新将x赋值为60。
(3)最后再执行构造方法,再重新将x赋值为58。
因此,在创建InitDemo类的对象d后,d的状态是其成员变量值为58。
4.5.2 静态变量的初始化
静态变量的初始化与实例变量的初始化类似,静态变量如果在声明时没有指定初值,编译器也将使用默认值为其赋初值。其主要方法有声明时初始化、使用静态初始化块、使用构造方法初始化。
注意:对于static变量,不论创建多少对象(甚至没有创建对象时)都只占一份存储空间。
1.静态初始化块
对于static变量除了可以使用前两种方法初始化外,还可以使用静态初始化块。静态初始化块是在初始化块前面加上static关键字。例如,下面的类定义就使用了静态初始化块。

注意:在静态初始化块中只能使用静态变量(就像静态方法中只能使用静态变量和调用静态方法一样),不能使用实例变量。
静态变量是在类装载时初始化的,因此在产生对象前就初始化了,这是使用类名访问静态变量的原因。
2.初始化顺序
当一个类有多种初始化方法时,执行顺序是:
(1)用默认值给静态变量赋值,然后执行静态初始化块为static变量赋值。
(2)用默认值给实例变量赋值,然后执行初始化块为实例变量赋值。
(3)最后使用构造方法初始化静态变量或实例变量。
4.5.3 垃圾回收器
在Java程序中,允许创建尽可能多的对象,而不用担心销毁它们。当程序使用一个对象后,该对象不再被引用时,Java运行系统就在后台自动运行一个线程,终结(finalized)该对象并释放其所占的内存空间,这个过程称为垃圾回收(garbage collection,GC)。
后台运行的线程称为垃圾回收器(garbage collector)。垃圾回收器自动完成垃圾回收操作,因此,这个功能也称为自动垃圾回收。所以,在一般情况下,程序员不用关心对象不被清除而产生内存泄露问题。
1.对象何时有可能被回收
当一个对象不再被引用时,该对象才有可能被回收。请看下面代码。

上面代码段创建了两个Employee对象emp、emp2,然后让emp2指向emp,这时emp2原来指向的对象没有任何引用指向它了,也没有任何办法得到或操作该对象了,该对象就有可能被回收了。
另外,也可明确删除一个对象的引用,这通过为对象引用赋null值即可,如下所示:

一个对象可能有多个引用,只有在所有的引用都被删除,对象才有可能被回收。请看下面代码。

上述语句执行后,只有原来a所指向的对象可以被回收。
2.强制执行垃圾回收器
尽管Java提供了垃圾回收器,但不能保证不被使用的对象及时回收。如果希望系统运行垃圾回收器,可以直接调用System类的gc()方法,如下所示:

另一种调用垃圾回收器的方法是通过Runtime类的gc()实例方法,如下所示:

注意:启动垃圾回收器并不意味着马上能回收无用的对象。因为,执行垃圾回收器需要一定的时间,且受各种因素如内存堆的大小、处理器的速度等的影响,因此垃圾回收器的真正执行是在启动垃圾回收器后的某个时刻才能执行。
4.5.4 变量作用域和生存期
变量的作用域(scope)是指一个变量可以在程序的什么范围内可以被使用。一般来说,变量只在其声明的块中可见,在块外不可见。若一个变量属于某个作用域,它在该作用域可见,即可被访问,否则不能被访问。
变量的生存期(lifetime)是指变量被分配内存的时间期限。当声明一个方法局部变量时,系统将为该变量分配内存,只要方法没有返回,该变量将一直保存在内存中。一旦方法返回,该变量将从内存栈中清除,它将不能再被访问。
对于对象,当使用new创建对象时,系统将在堆中分配内存。当一个对象不再被引用时,对象和内存将被回收。实际上是在之后某个时刻当垃圾回收期运行时才被回收。
Java程序的作用域是通过块实现的,块(block)是通过一对大括号指定的,块可对语句进行分组并定义了变量的作用域。下面代码说明了i、j和k三个变量的作用域。

这里,参数n、局部变量i和c的作用域在整个method()方法中;j的作用域在for循环体中;而tmp的作用域在while循环体中。这些变量离开了它们的作用域,其所占内存即被释放,将不能再访问它们。