5.1 用构造器确保初始化
定义类时,若未定义构造函数,则系统会自动创建一个无参构造器(默认构造器),若已经定义了构造器,则不会自动创建无参构造器。
5.2 方法重载
方法重载时,利用参数类型列表来区分方法,这里包括:参数类型、顺序、数量;
涉及基本类型的重载时,基本类型能从一个“较小”的类型自动提升至一个“较大”的类型。也就是说方法接受较小的基本类型作为参数,如果传入的实际参数较大,就得通过类型转换来执行窄化转换。
5.3 默认构造器
5.4 this关键字
为了能用简便、面向对象的语法来编写代码——即“发送消息给对象”,编译器做了一些幕后工作。它暗指把“所操作对象的引用”作为第一个参数传给类,所以如下的方法调用为:
//: initialization/BananaPeel.java
class Banana { void peel(int i) { /* ... */ } }
public class BananaPeel {
public static void main(String[] args) {
Banana a = new Banana(),
b = new Banana();
a.peel(1);
b.peel(2);
}
} ///:~
此时,在编译器内部,两个方法的调用变为:
Banana.peel(a,1)
Banana.peel(b,2)
另外,为一个类写了多个构造器,有时可能想在一个构造器中调用另一个构造器,以避免重复代码,此时使用this关键字。但是此时只能在构造器的开始处调用构造器,能且仅能调用一次。
5.5 清理:终结处理与垃圾回收
java为类提供了finalize方法,该方法可以在垃圾回收时刻做一些重要的清理工作。
对此,需要对垃圾回收做一个说明:对象可能不被垃圾回收;垃圾回收并不等于“析构”;垃圾回收只与内存有关。
此时,我们可以知道,如果使用了java的native方法(如调用c++new了一个对象),此时如果不明确的调用C++ delete掉这个对象,则垃圾回收器是无法回收这个对象的。另外,你做的一些其他操作可能也无法清楚,比如你在屏幕上绘制了一个图像,如果不显示擦除,则该图像永远保留在屏幕上。
如下是一个书籍借阅时签入的例子,所有的book对象在被当做垃圾回收前都应该被签入:
//: initialization/TerminationCondition.java
// Using finalize() to detect an object that
// hasn't been properly cleaned up.
class Book {
boolean checkedOut = false;
Book(boolean checkOut) {
checkedOut = checkOut;
}
void checkIn() {
checkedOut = false;
}
protected void finalize() {
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);
// Proper cleanup:
novel.checkIn();
// Drop the reference, forget to clean up:
new Book(true);
// Force garbage collection & finalization:
System.gc();
}
} /* Output:
Error: checked out
*///:~
垃圾回收器在内存快要耗尽时开始工作,回收不再使用的对象。很多虚拟机中堆的实现如下:它更像一个传送带,每分配一个新对象,它就往前移动一格。为了做到这一点,垃圾回收期需要整理内存,其有两种方法:“停止——复制”和“标记——清扫”。对于比较稳定的程序(没有新垃圾产生),其会进入“标记——清扫”模式。
另外,java虚拟机为了提高速度,使用了“即时(Juse-in-Time,JIT) ”编译技术,先将类编译为.class文件,然后再将该类的字节码装入内存。此时有两种方法:一种是让即时编译器编译所有代码;另一种是使用惰性评估,意思是即时编译器只在必要的时候才编译代码。
5.6 成员初始化
总结一下对象的创建过程,假设有名为dog的类:
1)即使没有显示的使用static关键字,构造器实际上也是静态方法。因此,首次创建类型为dog的对象时(构造器可以看成静态方法),或者dog类的静态方法/静态域首次被访问时,java解释器必须查找类路径,以定位dog.class文件。
2)然后载入dog.class(后面会学到,这将创建一个class对象),有关静态初始化的所有动作都会执行。因此,静态初始化只在class对象首次加载的时候进行一次。
3)的那个用new dog()创建对象时,首先将在堆上为dog对象分配足够的存储空间。
4)这块存储空间会被清零,这就自动地将dog对象中的所有基本类型数据都设置成默认值(对数字来说就是0,对布尔型和字符型也相同),而引用则被设置成null。
5)执行所有出现于字段定义处的初始化动作。
6)执行构造器。正如将在第7章所看到的,这可能会涉及到很多动作,尤其是涉及继承的时候。
显示静态初始化:
//: initialization/Spoon.java
public class Spoon {
static int i;
static {
i = 47;
}
} ///:~
static后面的代码跟静态初始化动作一样,这段代码仅执行一次:当首次生成这个类的一个对象时,或者首次访问属于那个类的静态数据成员时。
非静态实例初始化:
//: initialization/Mugs.java
// Java "Instance Initialization."
import static net.mindview.util.Print.*;
class Mug {
Mug(int marker) {
print("Mug(" + marker + ")");
}
void f(int marker) {
print("f(" + marker + ")");
}
}
public class Mugs {
Mug mug1;
Mug mug2;
{
mug1 = new Mug(1);
mug2 = new Mug(2);
print("mug1 & mug2 initialized");
}
Mugs() {
print("Mugs()");
}
Mugs(int i) {
print("Mugs(int)");
}
public static void main(String[] args) {
print("Inside main()");
new Mugs();
print("new Mugs() completed");
new Mugs(1);
print("new Mugs(1) completed");
}
} /* Output:
Inside main()
Mug(1)
Mug(2)
mug1 & mug2 initialized
Mugs()
new Mugs() completed
Mug(1)
Mug(2)
mug1 & mug2 initialized
Mugs(int)
new Mugs(1) completed
*///:~
这种语法对于支持“匿名内部类”的初始化是必须的,但是它使得你可以保证无论调用了哪个显示构造器,某些操作都会发生。从输出可以看出,实例初始化子句是在两个构造器之前执行的。
5.8 数组初始化
int[] a1={1,2,3,4,5,6,}//最后一个逗号可以不要
int a2[];
a2=a1;//此时a2是a1的引用。
另外,数组可以在程序中间用new来初始化。比如:a1=new int[rand.nextInt(20)]
可变参数列表有两种形式:jiuba