第五章 初始化与清理
现在总结的东西很多都需要用代码来帮助理解了,所以会有大量的测试代码,不过这中方式非常有用,如果认真敲过一遍之后,并且将这些代码弄清楚了,我相信你一定会对书中描述的内容有一个更清楚的认识。我是在eclipse工具上进行测试的,这里边的代码可以之间粘贴运行,不过对于程序员来说还是自己敲一遍吧,效果会更好。
5.5 清理 : 终结处理和垃圾回收
程序员都了解初始化的重要性,但常常会忘记同样也重要的清理工作。Java有垃圾回收器负责回收无用对象占据的内存资源。但是,垃圾回收器只知道释放那些经过new分配的内存,如果你的对象(并非使用new)获得了一块“特殊”的内存区域,则垃圾回收器就不知道该如何释放这块“特殊”内存。为了应对这种情况,Java允许在类中定义一个名为finalize()的方法。这个方法的工作原理:一旦垃圾回收器准备好释放对象占用的存储空间,将首先调用其finalize()方法,并且在下一次垃圾回收动作发生时,才会真正回收对象占用的内存。所以要是你打算用finalize(),就能在垃圾回收时刻做一些重要的清理工作。
注意:在Java中
1、对象可能不被垃圾回收。
2、垃圾回收并不等于“析构”。(析构是在c++里出现的一个概念,就是在c++中要销毁对象必须使用析构函数)
5.5.1 finalize()的用途何在
这里需要记住第三点:
3、垃圾回收只与内存有关
也就是说,使用垃圾回收器的唯一原因是为了回收程序不再使用的内存。所以对于与垃圾回收相关的任何行为来说(尤其是finalize()方法),它们也必须同内存及其回收有关。
5.5.2 你必须实施清理
要清理一个对象,用户必须在需要清理的时刻调用执行清理动作的方法。无论是“垃圾回收”还是“终结”,都不保证一定会发生。如果Java虚拟机(JVM)并未面临内存耗尽的情形,它是不会浪费时间去执行垃圾回收以恢复内存的。
5.5.3 终结条件
通常,不能指望finalize(),必须创建其他的“清理”方法,并且明确地调用他们。
class Book{
boolean checkedOut = false;
Book(boolean checkOut){
checkedOut=checkOut;
}
void checkIn(){
checkedOut=false;
}
protected void finalize(){
if(checkedOut){
System.out.println("error : checked out");
try {
super.finalize();
} catch (Throwable e) {
System.out.println("Throwable");
e.printStackTrace();
}
}
}
}
public class TerminationCondition {
public static void main(String[] args) {
Book novel = new Book(true);
novel.checkIn();
new Book(true);
System.gc();
}
}
运行结果:error : checked out
因为在main()方法中,由于程序员错误,有一本书未被签入。要是没有finalize()来验证终结条件,将很难发现这种缺陷。
5.5.4 垃圾回收器如何工作
5.6 成员初始化
Java尽力保证:所有变量在使用前都能得到恰当的初始化。对于方法的局部变量,Java以编译时错误的形式来贯彻这种保证。
例如:我们定义成员变量的时候并没有给它们赋值,但是编译器会给它们初始化默认值,比如 int i;在编译的时候初始值就是0。但是在方法中就不行了: void f(){
int i;
i++; //错误,i没有被初始化
}
5.6.1 指定初始化
如果想为某个变量赋初值,在基本数据类型中可以直接赋值。例如:int i=1; char ch='x' ; boolean bool=true;
如果是其他类型的对象则可以创建一个对象来初始化:Depth d=new Depth();
当然也可以通过调用方法来初始化:int i=f();
5.7 构造器初始化
public class Counter {
int i;
public Counter() {
i=7;
}
}
该程序中i首先会被置零,然后变成7。
5.7.1 初始化顺序
在类的内部,变量定义的先后顺序决定了初始化的顺序。即使变量定义散步与方法定义之间,它们仍旧会在任何方法(包括构造器)被调用之前得到初始化。
5.7.2 静态数据的初始化
无论创建多少个对象,静态数据都只占用一份存储区域。static关键字不能应用于局部变量,因此它只能作用于域。如果一个域是静态的基本类型域,且也没有对它进行初始化,那么它就会获得基本类型的标准初值;如果它是一个对象的引用,那么它的默认初始化值就是null。
class Bowl{
Bowl(int marker){
System.out.println("Bow1("+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("createing new cupboard in main");
new Cupboard();
System.out.println("createing new cupboard in main");
new Cupboard();
table.f2(1);
cupboard.f3(1);
}
static Table table=new Table();
static Cupboard cupboard=new Cupboard();
}
运行结果:
Bow1(1)
Bow1(2)
Table()
f1(1)
Bow1(4)
Bow1(5)
Bow1(3)
Cupboard
f1(2)
createing new cupboard in main
Bow1(3)
Cupboard
f1(2)
createing new cupboard in main
Bow1(3)
Cupboard
f1(2)
f2(1)
f3(1)
由运行结果可以发现:初始化的顺序是先对静态对象(前提是得有对象被初始化时才会进行,就是说静态初始化只有在必要时刻才会进行),而后是“非静态”对象,然后才是构造器。
5.7.3 显式的静态初始化
Java允许将多个静态初始化动作组织成一个特殊的“静态子句”(有时也叫做“静态块”)。
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);
}
// static Cups cups1=new Cups();
}
运行结果:
inside main
cup(1)
cup(2)
f(99)
静态块跟静态初始化一样也只执行一次,当首次生成这个类的一个对象时,或者首次访问属于那个类的静态数据成员(即便从未生成过那个类的对象)。就会被执行。
5.7.4 非静态实例初始化
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");
}
}
运行结果:
inside main()
Mug(1)
Mug(2)
mug1 & mug2 initialized
Mugs()
new Mugs() completed
Mug(1)
Mug(2)
mug1 & mug2 initialized
Mugs(int)
new Mugs(1) completed
从以上代码可以看出少了static关键字,所以会重复执行。
5.8 数组的初始化
数组的初始化有几种方法:
第一种:int [] a={a,b,c} 这种方式是你明确知道要初始化多少个数据,这种形式很有用,但缺点也很明显,就是更加受限。
第二种 : int [] a = new a [n] 这种形式可以解决你不知道具体要多少数据的难题,使用时可以根据情况而定。
5.9 枚举类型
在Java SE5中添加了一个看似很小的特性,即enum关键字,它使得我们在需要群组并使用枚举类型集时,可以很方便的处理。在很大程度上,可以将enum当作其他任何类来处理,事实上enum确实是类,并且具有自己的方法。enum有一个特别实用的特性,即ta3可以在switch语句内使用;
enum Spiciness{
NOT, MILD, MEDIUM, HOT, FLAMING
}
public class Burrito {
Spiciness degree;
public Burrito(Spiciness degree){
this.degree=degree;
}
public void describe(){
System.out.print("this burrito is ");
switch (degree) {
case NOT:
System.out.println("not spicy at all");
break;
case MILD:
case MEDIUM:
System.out.println("A little hot");
break;
case HOT:
case FLAMING:
default:
System.out.println("maybe too hot");
break;
}
}
public static void main(String[] args) {
Burrito
plain = new Burrito(Spiciness.NOT),
greenChile=new Burrito(Spiciness.MEDIUM),
jalapeno=new Burrito(Spiciness.HOT);
plain.describe();
greenChile.describe();
jalapeno.describe();
}
}
运行结果:
this burrito is not spicy at all
this burrito is A little hot
this burrito is maybe too hot