《Java编程思想》第5章 初始化与清理

书中源代码:https://github.com/yangxian1229/ThinkingInJava_SourceCode
构造器和垃圾回收器

5.1 用构造器确保初始化

构造器的名称与类名完全相同。
不接受任何参数的构造器叫做默认构造器,Java文档中通常使用术语无参构造器。在Java中,“初始化”和“创建”捆绑在一起,两者不能分离。
构造器是一类特殊的类型的方法,因为它没有返回值。

5.2 方法重载

5.2.1 区分重载方法

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

5.2.2 涉及基本类型的重载

如果传入的数据类型(实际参数类型)小于方法中声明的形式参数类型,实际数据类型就会被提升。char类型略有所不同,如果无法找到恰到好处接受char参数的方法,就会把char直接提升至int值。如果传入的实际参数较大,就得到通过类型转换来执行窄化转换。
根据方法的返回值来区分重载方法是行不通的。

5.3 默认构造器

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

5.4 this关键字

this关键字只能在方法内部使用,表示对“调用方法的那个对象”的引用。this的用法和其他对象引用并无不同。但要注意,如果在方法内部调用同一个类的另一个方法,就不必使用this,直接调用即可。只有当需要明确指出对当前对象的引用时,才需要使用this关键字。例如,当需要返回对当前对象的引用时,就常常在return语句里这样写:

public class Leaf {
	int i=0;
	Leaf increment(){//返回类型为Leaf
		i++;
		return this;
	}
}

this关键字对于将当前对象传递给其他方法时也很有用。

5.4.1 在构造器中调用构造器

可用this关键字做到这一点。

package ch5;

import static net.mindview.util.Print.*;

//改书中Flower例子
public class E09 {
	int petalCount = 0;
	String s = "initial value";
	E09(int petals){
		petalCount = petals;
		print("Constructor w/ int arg only, petalCount= "+petalCount);
	}
	E09(String ss){
		print("Constructor w/ String arg only, s= "+ss);
		s=ss;
	}
	E09(String s, int petals){
		this(petals);
	//! this(s); //Can't call two!
		this.s = s; //Another use of "this"
		print("String & int args");
	}
	E09(){
		this("hi",47);
		print("default constructor (no args)");
	}
	void printPetalCount(){
	//! this(11);//Not inside non-constructor!
		print("petalCount = "+petalCount+" s = "+s);
	}
	public static void main(String[] args) {
		E09 e09 = new E09();
		e09.printPetalCount();
	}
}/* Output:
Constructor w/ int arg only, petalCount= 47
String & int args
default constructor (no args)
petalCount = 47 s = hi
*///:~

构造器E09(String s, int petals)表明:尽管可以用this调用一个构造器,但却不能调用两个。此外,必须将构造器调用置于最起始处,否则编译器会报错。这个例子也展示了this的另一种用法。由于参数s的名称和数据成员s的名称相同,所以会产生歧义。使用this.s来代表数据成员就能解决这个问题。
**printPetalCount()**方法表明,除构造器之外,编译器禁止在其他任何方法中调用构造器。

5.4.2 static的含义

static(静态)方法就是没有this的方法。在static方法的内部不能调用非静态方法,反过来倒是可以。

5.5 清理:终结处理和垃圾回收

垃圾回收器只知道释放那些经由new分配的内存。
Java允许在类中定义一个名为finalize()的方法。它的工作原理“假定”是这样的:一旦垃圾回收器追备好释放对象占用的存储空间,将首先调用其finalize()方法,并且在下一次垃圾回收动作发生时,才会真正回收对象占用的内存。所以要是你打算用finalize(),就能在垃圾回收时刻做一些重要的清理工作。
1.对象可能不被垃圾回收。
2.垃圾回收并不等于“析构”。
这意味着在你不再需要某个对象之前,如果必须执行某些动作,那么你得自己去做。
也许你会发现,只要程序没有濒临存储空间用完的那一刻,对象占用的空间就总也得不到释放。如果程序执行结束,并且垃圾回收器一直都没有释放你创建的任何对象的存储空间,则随着程序的退出,那些资源也会全部交还给操作系统。这个策略是恰当的,因为垃圾回收本身也有开销,要是不用它,就不用支付这部分开销了。

5.5.1 finalize()的用途何在

3.垃圾回收只与内存有关。
也就是说,使用垃圾回收器的唯一原因是为了回收程序不再使用的内存。

5.5.2 你必须实施清理

无论是“垃圾回收”还是“终结”,都不保证一定会发生。如果Java虚拟机(JVM)并未面临内存耗尽的情形,它是不会浪费时间去执行垃圾回收以恢复内存的。

5.5.3 终结条件

通常,不能指望finalize(),必须创建其他的“清理”方法,并且明确地调用它们。看来finalize()只能存在于程序员很难用到的一些晦涩用法里了。不过,finalize()还有一个有趣的用法,它并不依赖于每次都要对finalize()进行调用,这就是对象终结条件的验证。

/*
 * Using finalize() to detect an object that hasn't been properly cleaned up.
 */
package ch5;

public class Book {
	boolean checkedOut = false;
	Book(boolean checkOut){
		checkedOut = checkOut;
	}
	void checkInt(){
		checkedOut = false;
	}
	protected void finalize() {
		if(checkedOut){
			System.out.println("Error: checked out");
		}
	//  Normally, you'll also do this:
	//  super.fianlize(); //Call the base-class version
	}
	public static void main(String[] args) {
		Book novel = new Book(true);
		//Proper cleanup:
		novel.checkInt();
		//Drop the reference, forget to clean up:
		new Book(true);
		//Force garbage collection & finalization
		System.gc();
	}
}/* Output:
Error: checked out
*///:~

本例的终结条件是:所有的Book对象在被当作垃圾回收前都应该被签入(check in)。但在main()方法中,由于程序猿的错误,有一本书未被签入。要是没有finalize()来验证终结条件,将很难发现这种缺陷。
System.gc()用于强制进行终结动作。即使不这么做,通过重复执行程序(假设程序将分配大量的存储空间而导致垃圾回收动作的执行),最终也能找出错误的Book对象。

5.5.4 垃圾回收器如何工作

Java虚拟机采用一种自适应的垃圾回收技术。

5.6 成员初始化

Java尽力保证:所有变量在使用前都能得到恰当的初始化。对于方法的局部变量,Java以编译时错误的形式来贯彻这种保证。
在类里定义一个对象引用时,如果不将其初始化,此引用就会获得一个特殊值null。

5.6.1 指定初始化

如果想要为某个变量赋初值,可以在定义类成员变量的地方为其赋值。

5.7 构造器初始化

可以用构造器来进行初始化,在运行时刻,可以调用方法或执行某些动作来确定初值。但要牢记:无法阻止自动初始化的进行,它将在构造器被调用前发生。

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

那么i首先会被置0,然后变成7。

5.7.1 初始化顺序

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

5.7.2 静态数据的初始化

总结一下对象的创建过程,假设有个名为Dog的类:
1.即使没有显式地使用static关键字,构造器实际上也是静态方法。因此,当首次创建类为Dog的对象时,或者Dog类的静态方法/静态域首次被访问时,Java解释器必须查找类路径,假设有个名为Dog.class文件。
2.然后载入Dog.class,有关静态初始化的所有动作都会执行。因此,静态初始化只在Class对象首次加载的时候进行一次。
3.当用new Dog()创建对象的时候,首先将在堆上为Dog对象分配足够的存储空间。
4.这块存储空间会被清零,这就自动地将Dog对象中的所有基本类型数据都设置成了默认值,而引用则被设置成了null
5.执行所有出现于字段定义处的初始化动作。
6.执行构造器。

5.7.3 显式的静态初始化

5.7.4 非静态实例初始化

5.8 数组初始化

0~length-1

5.8.1 可变参数列表

void f(int a,String...ss){

在可变参数列表中可以使用任何类型的参数,包括基本类型。

5.9 枚举类型

enum

猜你喜欢

转载自blog.csdn.net/lanzijingshizi/article/details/84028590