《java编程思想》_第五章_初始化与清理

时间:2022-10-02 19:41:50

    初始化和清理是涉及安全的两个问题,java中采用了构造器,并额外提供了“垃圾回收器”,对于不再使用的内存资源,垃圾回收器能自动将其释放。

一、用构造器确保初始化

    java中,通过提供构造器,类的设计者可以确保每个对象都会得到初始化。创建对象时,如果其类具有构造器,java就会在用户有能力操作对象之前自动调用相应的构造器,从而保证了初始化的进行。考虑到在初始化期间要自动调用构造器,所以构造器与类的名称必须相同。

public class IfElse {

	public static void main(String[] args){
		new Test();//在创建对象时,会为对象分配存储空间,并调用相应的构造器。
	}

}

class Test{
	Test(){
		System.out.println("this is test");
	}
}

运行结果:this is test

不接受任何参数的构造器叫做默认构造器(无参构造器),构造器也能带有形式参数,以便指定如何创建对象。

public class IfElse {

	public static void main(String[] args){
		new Test(5);
	}

}

class Test{
	Test(int i){
		System.out.println("this is test"+i);
	}
}

运行结果:this is test5

从概念上讲,“初始化”与“创建”是彼此独立的,然而在java中,“初始化”与“创建”捆绑在一起,两者不能分离。


二、方法重载

    1.区分重载方法

    重载的方法具有相同的名字,但是每个重载的方法都必须有一个独一无二的参数列表,甚至参数顺序的不同也足以区分两个方法,不过一般情况下别这么做,这会使代码难以维护。

    2.涉及基本类型的重载

    基本类型能从一个“较小”的类型自动提升为一个“较大”的类型,此过程一旦涉及到重载,可能会造成一些混淆。如果传入的数据类型(实际参数类型),小于方法中声明的形式参数类型,实际数据类型就会被提升。char型略有不同,如果无法找到刚好接受char参数的方法,就会把char直接提升至int。如果传入的实际参数类型大于方法中声明的参数类型,就要通过类型转化做窄化转换,否则编译器会报错。 


三、默认构造器

    如果写的类中没有构造器,则编译器会自动帮你创建一个默认的构造器。但是如果已经定义了一个构造器(无论是否有参数),编译器就不会帮你自动创建默认构造器。


四、this关键字

    1.在构造器中调用构造器

    可能为一个类写了多个构造器,有时可能会想在一个构造器中调用另一个构造器,以免重复代码,可以用this关键字做到。通常写this关键字的时候,都是指“这个对象”或者“当前对象”,而且它本身表示对当前对象的引用。

    

public class Flower {
	
	int petalCount = 0;
	String s = "initial value";
	
	Flower(int petals){
		petalCount = petals;
		System.out.println("int arg only,petalCount = "+petalCount);
	}
	
	Flower(String ss){
		System.out.println("String arg only,s = "+s);
		s = ss;
	}
	
	Flower(String s,int petals) {
		this(petals);
		//this(s);此处编译器会报错
		this.s = s;
		System.out.println("String & int arg");
	}
	
	Flower() {
		this("hi",47);
		System.out.println("no args");
	}
	
	public static void main(String args[]){
		Flower x = new Flower();
	}
}

运行结果:int arg only,petalCount = 47
                String & int arg
                no args

在有两个参数的构造方法中,试图调用两次构造方法,但是编译器报错。这表明,尽管可以用this调用一个构造器,但却不能调用两个,此外,必须将构造器置于最初始处,否则编译器会报错。

  

五、清理:终结处理和垃圾回收

    java有垃圾回收器负责回收无用对象占据的内存资源,但也有特殊情况:假定你的对象(并非使用new关键字)获得了一块“特殊”的内存区域,由于垃圾回收器只知道释放那些经由new分配的内存,所以它不知道该如何释放该对象的这块“特殊”内存。为了应对这种情况,java允许在类中定义一个名为finalize()的方法。它的工作原理“假定”是这样的:一旦垃圾回收器准备好释放对象占用的存储空间,将首先调用其finalize()方法,并且在下一次垃圾回收动作发生时,才会真正回收对象占用的内存。要记住以下三点:

    (1)对象可能不被垃圾回收

    (2)垃圾回收不等于“析构”

    (3)垃圾回收只与内存有关

    1.垃圾回收器如何工作

    在java虚拟机中,堆的实现像是一个传送带,每分配一个新对象,它就往前移动一格。这意味着对象存储空间分配的速度非常快,java的“堆指针"只是简单地移动到尚未分配的区域。但实际情况未必像传送带那样,因此,高效率的垃圾回收依赖于垃圾回收器的介入。当它工作时,将一面回收空间,一面使堆中的对象紧凑排列,这样,”堆指针“就可以很容易地移动到更靠近传送带的开始处。通过垃圾回收器对对象的重新排列,实现了一种高速的,有无限空间可以分配的堆模型。

    垃圾回收机制了解:

(1)引用记数:简单,但速度很慢。每个对象都含有一个引用计数器,当有引用连接至对象时,引用数加1,当引用离开作用域或被置于null时,引用数减1。这个方法有个缺陷,如果对象之间循环引用,可能会出现”对象应该被回收,但引用数却不为0“的情况。对于垃圾回收器而言,定位这样的交互自引用的对象组所需工作量极大。引用数常用来说明垃圾收集的工作方式,但似乎从未被应用于任何一种虚拟机。

(2)自适应:java虚拟机采用自适应的垃圾回收技术,停止-复制:先暂停程序,将所有存活的对象复制到另一个堆中,没有被复制的即为垃圾;标记-清扫:遍历所有引用,进而找出所有存活的对象,当没有引用时,变给对象标记,这个过程中不会有任何清理操作。在java虚拟机运行过程中,根据堆空间的对象情况,在以上两种状态切换,以达到垃圾回收。

   

六、成员初始化

    java尽力保证,所有的变量在使用前都能得到恰当的初始化,对于方法的局部变量,java以编译时错误的形式来贯彻这种保证。

void f(){
		int i;
		i++;
	}

以上代码编译期会报错变量没有初始化。但是对于类的数据成员,情况有所不同,类的每个基本数据成员保证都会有一个初始值。即使代码没有给出,编译器也会自动赋初值。


七、构造器初始化

    1.初始化顺序

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

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

}

运行结果:window(1)
                 window(2)
                 window(3)
                 House()

                 window(33)

                 f()

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

    2.静态数据的初始化

    无论创建多少个对象,静态数据都只占一份存储区域。static关键字不能应用于局部变量,因此它只能作用于域。 初始化的顺序是先静态对象(如果之前并未被初始化),而后是非静态对象。

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

    (1)当首次创建类型为Dog的对象时,java解释器查找类路径,定位Dog.class文件

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

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

    (4)这块存储空间会被清零,这就自动地将Dog对象中的所有基本类型数据都设置成了默认值,而引用则被置为null。

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

    (6)执行构造器。

    

八、数组初始化

    数组只是相同类型的,用一个标识符名称封装到一起的一个对象序列或者基本类型数据序列。 

    可变参数:String[] args的写法可以写为String...args。有了可变参数,就不用显示地编写数组语法了,当指定参数时,编译器会去填充数组,但是如果传递的本来就是数组,编译器就不会再执行任何转换。因此,如果你有一组事物,可以把它们当作列表传递,而如果你已经有了一个数组,该方法可以把它们当作可变参数列表来接受。将0个参数传递给可变参数列表依旧是可行的。


九、枚举类型

    java SE5中添加了一个看似很小的特性,即enum关键字,它使得我们在需要群组并使用枚举类型集时,可以很方便地处理。

enum Simple {
	NOT,MILD,MEDIUM,HOT
}

public class SimpleEnumUse{
	public static void main(String[] args){
		Simple howHot = Simple.MEDIUM;
		System.out.println(howHot);
	}
}

运行结果:MEDIUM

enum有一个特别实用的特性,即它可以在switch语句内使用。