5.1 用构造器确保初始化
构造器有助于减少错误,并使代码更易于阅读。在java中,初始化与创建捆绑在一起,两者不能分离。
构造器没有任何返回值。new表达式确实返回了对新建对象的引用,但构造器本身并没有任何返回值。
5.2 方法重载
每一个重载方法都必须有一个独一无二的参数类型列表。
参数顺序不同也足以区分两个方法,不过一般情况下不这么做,因为这回使得代码难以维护。
如果传入的数据类型(实际参数类型)小于方法中声明的形式参数类型,实际数据类型就会被提升。char型略有不同,如果无法找到恰好接受char参数的方法,就会把char直接提升至int型。如果传入的实际参数较大,就得通过类型转换来执行窄化转换,否则编译器会报错。
根据方法的返回值来区分重载方法是行不通的。
如果已经定义了一个构造器(无论是否有参数),编译器就不会帮你自动创建默认构造器。
5.4 this关键字
this关键字只能在方法内部使用,标识对“调用发放的那个对象”的引用。
this可以返回对当前对象的引用,在return中返回,也可以在方法传递中作为参数调用。
//: initialization/Flower.java
// Calling constructors with "this"
import static net.mindview.util.Print.*;
public class Flower {
int petalCount = 0;
String s = "initial value";
Flower(int petals) {
petalCount = petals;
print("Constructor w/ int arg only, petalCount= "
+ petalCount);
}
Flower(String ss) {
print("Constructor w/ String arg only, s = " + ss);
s = ss;
}
Flower(String s, int petals) {
this(petals);
//! this(s); // Can't call two!
this.s = s; // Another use of "this"
print("String & int args");
}
Flower() {
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) {
Flower x = new Flower();
x.printPetalCount();
}
} /* Output:
Constructor w/ int arg only, petalCount= 47
String & int args
default constructor (no args)
petalCount = 47 s = hi
*///:~
尽管可以用this调用一个构造器,但却不能调用两个,必须将构造器调用置于最起始处,否则编译器会报错。除构造器之外,编译器禁止在其他任何方法中调用构造器。
5.5 清理:终结处理和垃圾回收
一旦垃圾回收器准备好释放对象占用的存储空间,将首先调用其finalize方法,并在下一次垃圾回收动作发生时,才会真正回收对象占用的内存。
- 对象可能不被垃圾回收;
- 垃圾回收并不等于“析构”;
- 垃圾回收只与内存有关。
使用垃圾回收器的唯一原因是为了回收程序不再使用的内存。
对finalize()的需求限制到一种特殊情况,即通过某种创建对象方式以外的方式为对象分配了存储空间。
finalize还有一个有趣的用法,它并不依赖于每次都要对finalize进行调用,这就是对象终结条件的验证。
package chapter5;
/**
* Created by Blue on 2017/8/29.
*/
class WebBank {
boolean loggedIn = false;
WebBank(boolean logStatus) {
loggedIn = logStatus;
}
void logIn() {
loggedIn = true;
}
void logOut() {
loggedIn = false;
}
protected void finalize() {
if (loggedIn)
System.out.println("Error: still logged in");
try {
super.finalize();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}
}
public class Exercise10 {
public static void main(String[] args) {
WebBank bank1 = new WebBank(true);
WebBank bank2 = new WebBank(true);
bank1.logOut();
new WebBank(true);
System.gc();
}
}
System.gc()用于强制进行终结动作,既是不这么做,通过重复地执行程序,假设程序将分配大量的存储空间而导致垃圾回收动作的执行。
5.6 成员初始化
方法的局部变量在使用前必须设置初始值。
5.7 构造器初始化
类的内部,变量定义的先后顺序决定了初始化的顺序,即使变量定义散布于方法定义之间,它们仍旧会在任何方法(包括构造器)被调用之前得到初始化。
无论创建多少个对象,静态数据都只占用一份存储区域,static关键字不能应用于局部变量,因此它只能作用于域。
静态初始化只有在必要时刻才会进行,静态对象不会再次被初始化。
对象的创建过程,假设有个名为Dog的类:
- 即使没有显式地使用static关键字,构造器实际上也是静态方法。因此,当首次创建类型为Dog的对象时(构造器可以看成静态方法),或者Dog类的静态方法/静态域首次被访问时,Java解释器必须查找类路径,以定位Dog.class文件。
- 然后载入Dog.class,有关静态初始化的所有动作都会执行,静态初始化只在Class对象首次加载的时候进行一次。
- 当用new Dog()创建对象的时候,首先将在堆上为Dog对象分配足够的存储空间。
- 这块存储空间会被清零,这就自动地将Dog对象中的所有基本类型数据都设置成了默认值,而引用则被设置成null。
- 执行所有出现于字段定义处得初始化动作。
- 执行构造器。
代码块优先于构造器初始化。
5.8 数组初始化
有了可变参数,就再也不用显式地编写数组语法了,当你指定参数时,编译器实际上会为你填充数组。0个参数传递给可变参数是可行的。
public class OptionalTrailingArguments {
static void f(int required, String... trailing) {
System.out.print("required: " + required + " ");
for(String s : trailing)
System.out.print(s + " ");
System.out.println();
}
public static void main(String[] args) {
f(1, "one");
f(2, "two", "three");
f(0);
}
} /* Output:
required: 1 one
required: 2 two three
required: 0
*///:~
可变参数列表与自动包装机制可以和谐共处。
5.9 枚举类型
enum有一个特别实用的特性,即它可以在switch语句内使用。