对象的初始化和清理
"不安全"的编程是造成编程代价昂贵的罪魁祸首之一。有两个安全性问题:初始化和清理。
初始化
构造器
在java中利用构造器初始化,因为构造器能保证进行正确的初始化和清理(没有正确的构造器调用,编译器就不允许创建对象),所以你就有了完全的控制和安全。
构造器分为:
- 无参构造器(默认)
- 有参构造器
一旦你显式地定义了构造器(无论有参还是无参),编译器就不会自动为你创建无参构造器。
创建一个对象时,内存被分配,构造器被调用。构造器保证了对象在你使用它之前进行了正确的初始化。
有参,无参构造器可以同时存在在一个类里面,两个构造器方法的方法名相同,怎么区分他们呢?
方法重载
方法重载(Overloading)的定义:如果有两个方法的方法名相同,但参数不一致,哪么可以说一个方法是另一个方法的重载。例如:默认构造器(不带参),带参构造器,方法名相同。
区分重载方法:有一条简单的规则:每个被重载的方法必须有独一无二的参数列表。
重载中的基本类型:如果传入的参数类型大于方法期望接收的参数类型,你必须首先做下转换,如果你不做的话,编译器就会报错。
this关键字
通过 this 关键字返回当前对象的引用
通过this关键字,在构造器中调用构造器
- 只能通过 this 调用一次构造器
- this 关键字只能在非静态方法内部使用
- 必须首先调用构造器,否则编译器会报错
- 编译器不允许你在一个构造器之外的方法里调用构造器
静态方法
static 方法中不会存在 this,静态方法是为类而创建的,不需要任何对象。一个类中的静态方法可以访问其他静态方法和静态属性
Java 尽量保证所有变量在使用前都能得到恰当的初始化。例如:指定初始化,构造器初始化
几种常见初始化(初始化顺序:在类中变量定义的顺序决定了它们初始化的顺序):
- 静态数据的初始化
class Bowl {
Bowl(int marker) {
System.out.println("Bowl(" + 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("main creating new Cupboard()");
new Cupboard();
System.out.println("main creating new Cupboard()");
new Cupboard();
table.f2(1);
cupboard.f3(1);
}
static Table table = new Table();
static Cupboard cupboard = new Cupboard();
}
- 显式的静态初始化
public class Spoon {
static int i;
//"静态子句"(有时叫做静态块)
static {
i = 47;
}
}
- 非静态实例初始化
class Mug {
Mug(int marker) {
System.out.println("Mug(" + marker + ")");
}
}
public class Mugs {
Mug mug1;
Mug mug2;
{ // [1]
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");
}
}
- 数组初始化
- 动态数组创建
枚举类型
由于 switch 是在有限的可能值集合中选择,因此它与 enum 是绝佳的组合。注意,enum 的名称是如何能够倍加清楚地表明程序的目的的。
清理
垃圾回收器
-
对象可能不被垃圾回收。
-
垃圾回收不等同于析构。
-
垃圾回收只与内存有关。
C++中你必须实施清理,Java中无论是"垃圾回收"还是"终结",都不保证一定会发生。如果 Java 虚拟机(JVM)并未面临内存耗尽的情形,它可能不会浪费时间执行垃圾回收以恢复内存。
finalize的用途
本地方法目前只支持 C 和 C++,但是它们可以调用其他语言写的代码,所以实际上可以调用任何代码。在非 Java 代码中,也许会调用 C 的 malloc()
函数系列来分配存储空间,而且除非调用 free()
函数,不然存储空间永远得不到释放,造成内存泄露。但是,free()
是 C 和 C++ 中的函数,所以你需要在 finalize()
方法里用本地方法调用它。
终结条件
class Book {
boolean checkedOut = false;
Book(boolean checkOut) {
checkedOut = checkOut;
}
void checkIn() {
checkedOut = false;
}
@Override
protected void finalize() throws Throwable {
if (checkedOut) {
System.out.println("Error: checked out");
}
// Normally, you'll also do this:
// super.finalize(); // Call the base-class version
}
}
public class TerminationCondition {
public static void main(String[] args) {
Book novel = new Book(true);
//正确清理
novel.checkIn();
//删除参考,忘记清理
new Book(true);
//强制垃圾收集和终结
//用于强制进行终结动作。
//但是即使不这么做,只要重复地执行程序(假设程序将分配大量的存储空间而导致垃圾回收动作的执行),
//最终也能找出错误的 Book 对象。
System.gc();
try
{
Thread.sleep(2000);
}
catch(InterruptedException ex)
{
Thread.currentThread().interrupt();
}
}
}
输出:
Error: checked out
本例的终结条件是:所有的 Book 对象在被垃圾回收之前必须被登记。但在 main()
方法中,有一本书没有登记。要是没有 finalize()
方法来验证终结条件,将会很难发现这个 bug。
扩展:垃圾回收器如何工作