书中源代码: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