四、初始化
四、初始化
初始化(initialization)和清理(cleanup)正是导致“不安全”编程的两个因素。许多C程序的错误都源于程序员忘记初始化变量。特别是使用依赖库时,用户可能不知道如何初始化库的组件,甚至不知道必须要初始化它们。清理也需要特别关注,因为当你不再使用一个元素时,就不再关注,所以很容易就会忘记它。如此一来,这个元素使用的资源会一直被占用,结果就是资源很容易被耗尽(尤其是内存)。
C++引入了构造器(constructor)的概念,它是在创建对象时被自动调用的特殊方法。Java也采用了构造器,并且还提供了一个垃圾收集器(garbage collector)。当不再使用内存资源的时候,垃圾收集器会自动将其释放。
4.1 用构造器保证初始化
下面是一个带有构造器的简单类:
class Rock {
Rock() { // This is the constructor
System.out.print("Rock ");
}
}
public class SimpleConstructor {
public static void main(String[] args) {
for(int i = 0; i < 10; i++)
new Rock();
}
}
/* Output:
Rock Rock Rock Rock Rock Rock Rock Rock Rock Rock
*/
让构造器接受一个参数:
class Rock2 {
Rock2(int i) {
System.out.print("Rock " + i + " ");
}
}
public class SimpleConstructor2 {
public static void main(String[] args) {
for(int i = 0; i < 8; i++)
new Rock2(i);
}
}
/* Output:
Rock 0 Rock 1 Rock 2 Rock 3 Rock 4 Rock 5 Rock 6 Rock 7
*/
重载构造器:
class Tree {
int height;
Tree() {
System.out.println("Planting a seedling");
height = 0;
}
Tree(int initialHeight) {
height = initialHeight;
System.out.println("Creating new Tree that is " +
height + " feet tall");
}
void info() {
System.out.println(
"Tree is " + height + " feet tall");
}
void info(String s) {
System.out.println(
s + ": Tree is " + height + " feet tall");
}
}
public class Overloading {
public static void main(String[] args) {
for(int i = 0; i < 5; i++) {
Tree t = new Tree(i);
t.info();
t.info("overloaded method");
}
// Calls overloaded constructor:
new Tree();
}
}
/* Output:
Creating new Tree that is 0 feet tall
Tree is 0 feet tall
overloaded method: Tree is 0 feet tall
Creating new Tree that is 1 feet tall
Tree is 1 feet tall
overloaded method: Tree is 1 feet tall
Creating new Tree that is 2 feet tall
Tree is 2 feet tall
overloaded method: Tree is 2 feet tall
Creating new Tree that is 3 feet tall
Tree is 3 feet tall
overloaded method: Tree is 3 feet tall
Creating new Tree that is 4 feet tall
Tree is 4 feet tall
overloaded method: Tree is 4 feet tall
Planting a seedling
*/
4.2 无参构造器
如前所述,无参构造器(又叫“默认构造器”或“零参数构造器”)是没有参数的构造器,用于创建“默认对象”。如果你创建了一个没有构造器的类,编译器会自动为这个类添加一个无参构造器。
// housekeeping/DefaultConstructor.java
class Bird {}
public class DefaultConstructor {
public static void main(String[] args) {
Bird b = new Bird(); // 默认!
}
}
语句
new Bird()
创建了一个新对象,并调用其无参构造器,尽管你并没有明确定义它。没有无参构造器的话,就没有方法可以用来创建对象。不过如果你已经定义了一个构造器,无论是否有参数,编译器都不会再帮你自动创建一个无参构造器了
4.3 在构造器中调用构造器
public class Flower {
int petalCount = 0;
String s = "initial value";
Flower(int petals) {
petalCount = petals;
System.out.println(
"Constructor w/ int arg only, petalCount= "
+ petalCount);
}
Flower(String ss) {
System.out.println(
"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"
System.out.println("String & int args");
}
Flower() {
this("hi", 47);
System.out.println("Zero-argument constructor");
}
void printPetalCount() {
//- this(11); // Not inside non-constructor!
System.out.println(
"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
Zero-argument constructor
petalCount = 47 s = hi
*/
4.4 静态数据的初始化
无论创建了多少对象,静态数据都只有一份存储空间。static关键字不能用于局部变量,而仅适用于字段(成员变量)。如果一个字段是static的基本类型,并且没有初始化,那它就会获得基本类型的标准初始值。如果它是一个对象引用,则默认初始值为null。
如果将静态数据的初始化放在定义时,采取的方式和非静态变量没有什么不同。
下面的示例显示了static何时被初始化:
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();
}
/* Output:
Bowl(1)
Bowl(2)
Table()
f1(1)
Bowl(4)
Bowl(5)
Bowl(3)
Cupboard()
f1(2)
main creating new Cupboard()
Bowl(3)
Cupboard()
f1(2)
main creating new Cupboard()
Bowl(3)
Cupboard()
f1(2)
f2(1)
f3(1)
*/
Bowl代码里的提示信息揭示了一个类的创建过程,Table和Cupboard类把Bowl类型的静态字段分散到各处定义。注意Cupboard类还在static定义之前创建了一个非静态的bowl3。
输出显示了static初始化仅在必要时发生。如果不创建Table对象并且也不引用Table.bowl1或Table.bowl2,则Bowl类型的静态字段bowl1和bowl2永远都不会被创建。它们仅在第一个Table对象创建(或第一次访问静态数据)时被初始化。之后,这些静态对象不会被重新初始化。
初始化的顺序是从静态字段开始(如果它们还没有被先前的对象创建触发初始化的话),然后是非静态字段。可以从输出中看到这一点。要执行静态的main()方法,必须先加载StaticInitialization类,然后初始化它的静态字段table和cupboard,这会导致它们对应的类被加载,并且因为它们都包含了静态的Bowl对象,所以Bowl也被加载。因此,这个特定程序中所有的类都在main()方法开始执行前加载。通常情况下并非如此,这是因为常见的程序中不会像此例那样通过static将所有内容链接在一起。
4.5 数组初始化
数组是一个对象序列或基本类型序列,其中含有的元素类型相同,用一个标识符名字打包在了一起。数组通过方括号索引操作符(indexing operator)[ ]来定义和使用。要定义一个数组引用,在类型名字后面加上空方括号即可:
int[] a1;
也可以将方括号置于标识符后面,效果是一样的:
int a1[];
这种格式符合C和C++程序员的习惯。然而,前一种格式可能更合理,因为它表示这个类型是“一个int数组”。本书采用了这种格式。