C++引入了构造器的概念,这是一个在创建对象时被自动调用的特殊方法。Java中也采用了构造器,并额外提供了“垃圾回收器”。对于不再使用的内存资源,垃圾回收器能自动将其释放。
5.1 用构造器确保初始化
在Java中,通过提供构造器,类的设计者可确保每个对象都会初始化。创建对象时,如果其类具有构造器,Java就会在用户有能力操作对象之前自动调用相应的构造器,从而保证了初始化的进行。
构造器是一种特殊类型的方法,因为它没有返回值。这与返回值为空(void)明显不同。对于空返回值,尽管方法本身不会自动返回什么,但仍可选择让它返回别的东西。构造器则不会返回任何东西,你别无选择。
5.2 方法重载
5.2.1 区分重载方法
要是几个方法有相同的名字,Java如何才能知道你指的是哪一个呢?其实规则很简单:每个重载的方法都必须有一个独一无二的参数类型列表。
5.2.3 以返回值区分重载方法
下面两个方法,虽然它们有同样的名字和形式参数,但却很容易区分它们:
void f() {}
int f() { return 1; }
只要编译器可以根据语境明确判断语义,比如在int x=f()中,那么的确可以据此区分重载方法。如果像下面这样调用方法:
f();
此时Java如何才能判断该调用哪一个f()呢?因此,根据方法的返回值来区分重载方法是行不通的。
5.4 this 关键字
假设你希望在方法的内部获得对当前对象的引用。由于这个引用是由编译器“偷偷”传入的,所以没有标识符可用。但是,为此有个专门的关键字:this。this关键字只能在方法内部使用,表示对“调用方法的那个对象”的引用。this的用法和其他对象引用并无不同。但要注意,如果在方法内部调用同一个类的另一个方法,就不必使用this,直接调用即可。
只有当需要明确指出对当前对象的引用时,才需要使用this关键字。例如,当需要返回对当前对象的引用时,就常常在return语句里这样写:
public class Leaf{
int i = 0;
Leaf increment() {
i++;
return this;
}
void print() {
System.out.println("i = " + i);
}
public static void main(Stirng[] args) {
Leaf x = new Leaf();
x.increment().increment().increment().print();
}
}/* Output
i = 3
*/
由于increment()通过this关键字返回了对当前对象的引用,所以很容易在一条语句里对同一个对象执行多次操作。
5.7 构造器初始化
5.7.1 初始化顺序
在类的内部,变量定义的先后顺序决定了初始化的顺序。即使变量定义散布于方法定义之外,它们仍然会在任何方法(包括构造器)被调用之前得到初始化。
5.7.2 静态数据的初始化
无论创建多少个对象,静态数据都只占用一份存储区域。static关键字不能应用于局部变量,因此它只能作用于域。如果一个域是静态的基本类型域,且也没有对它进行初始化,那么它就会获得基本类型的标准初值;如果它是一个对象引用,那么它的默认初始值就是null。
总结一下对象的创建过程,假设有一个名为dog的类:
1.即使没有显式地使用static关键字,构造器实际上也是静态方法。因此,当首次创建类型为Dog的对象时(构造器可以看成静态方法),或者Dog类的静态方法/静态域首次被访问时,Java解释器必须查找类路径,以定位Dog.class文件。
2.然后载入Dog.class,有关静态初始化的所有动作都会执行。因此静态初始化只在Class对象首次加载的时候进行一次。
3.当用new Dog()创建对象的时候,首先将在堆上为Dog对象分配足够的存储空间。
4.这块存储空间会被清零,这就自动地将Dog对象中所有地基本类型数据都设置成了默认值,而引用则被设置成了null。
5.执行所有出现于字段定义处的初始化动作。
6.执行构造器。
5.8 数组初始化
数组只是相同类型的、用一个标识符名称封装到一起的一个对象序列或者基本类型数据序列。要定义一个数组,只需在类型名后加上一对空方括号即可:
int[] a1;
方括号也可以置于标识符后面:
int a1[];
两种格式的含义是一样的,后一种格式符合C和C++程序员的习惯。不过,前一种格式或许更合理,毕竟它表明类型是“一个int型数组”。
所有数组(无论它们的元素是对象还是基本类型)都有一个固有成员,可以通过它获知数组内包含多少个元素,但不能对其修改。这个成员就是length。
5.8.1 可变参数列表
可变参数列表应用于参数个数或类型未知的场合。
public class NewVarArgs {
static void printArray(Object... args) {
for(Object obj : args)
System.out.print(obj + " ");
System.out.println();
}
public static void main(String[] args) {
printArray(new Integer(47),new Float(3.14),
new Double(11.11));
printArray(47, 3.14F, 11.11);
printArray("one", "two", "three");
}
}/* Output
47 3.14 11.11
47 3.14 11.11
one two three
*/
有了可变参数,就再也不用显式地编写数组语法了,当你指定参数是,编译器实际上会为你去填充数组。
5.9 枚举类型
Java的enum功能比C/C++中的枚举类型要完备得多。下面是一个简单的例子:
public enum Spiciness {
NOT , MILD , MEDIUM , HOT , FLAMING;
}
由于枚举类型的实例是常量,因此按照命名惯例它们都用大写字母表示。
enum有一个特别实用的特性,即它可以在switch语句内使用:
public class Burrito {
Spiciness degree;
public Burrito(Spiciness degree){ this.degree = degree;}
public void describe() {
System.out.print("This burrito is");
switch(degree) {
case NOT: System.out.println("not spicy at all.");
case MILD:
case MEDIUM: System.out.println("a little hot.");
case HOT:
case FLAMING:
default: System.out.println("may be too hot.");
}
}
public static void main(String args) {
Burrito plain = new Burrito(Spiciness.NOT);
Burrito green = new Burrito(Spiciness.MEDIUM);
Burrito jalapeno = new Burrito(Spiciness.HOT);
plain.describe();
green.describe();
jalapeno.describe();
}
}/* Output:
This burrito is not spicy at all.
This burrito is a little hot.
This burrito is maybe too hot.
*/
大体上,你可以将enum用作另外一种创建数据类型的方式,然后直接将所得到的类型拿来使用。