按键盘上方向键 ← 或 → 可快速上下翻页,按键盘上的 Enter 键可回到本书目录页,按键盘上方向键 ↑ 可回到本页顶部!
————未阅读完?加入书签已便下次继续阅读!
…………………………………………………………Page 113……………………………………………………………
} ///:~
在Card 中,Tag 对象的定义故意到处散布,以证明它们全都会在构建器进入或者发生其他任何事情之前得到
初始化。除此之外,t3 在构建器内部得到了重新初始化。它的输入结果如下:
Tag(1)
Tag(2)
Tag(3)
Card()
Tag(33)
f()
因此,t3 句柄会被初始化两次,一次在构建器调用前,一次在调用期间(第一个对象会被丢弃,所以它后来
可被当作垃圾收掉)。从表面看,这样做似乎效率低下,但它能保证正确的初始化——若定义了一个过载的
构建器,它没有初始化 t3;同时在t3 的定义里并没有规定“默认”的初始化方式,那么会产生什么后果
呢?
2。 静态数据的初始化
若数据是静态的(static),那么同样的事情就会发生;如果它属于一个基本类型(主类型),而且未对其
初始化,就会自动获得自己的标准基本类型初始值;如果它是指向一个对象的句柄,那么除非新建一个对
象,并将句柄同它连接起来,否则就会得到一个空值(NULL )。
如果想在定义的同时进行初始化,采取的方法与非静态值表面看起来是相同的。但由于static 值只有一个存
储区域,所以无论创建多少个对象,都必然会遇到何时对那个存储区域进行初始化的问题。下面这个例子可
将这个问题说更清楚一些:
//: StaticInitialization。java
// Specifying initial values in a
// class definition。
class Bowl {
Bowl(int marker) {
System。out。println(〃Bowl(〃 + marker + 〃)〃);
}
void f(int marker) {
System。out。println(〃f(〃 + marker + 〃)〃);
}
}
class Table {
static Bowl b1 = new Bowl(1);
Table() {
System。out。println(〃Table()〃);
b2。f(1);
}
void f2(int marker) {
System。out。println(〃f2(〃 + marker + 〃)〃);
}
static Bowl b2 = new Bowl(2);
}
class Cupboard {
Bowl b3 = new Bowl(3);
112
…………………………………………………………Page 114……………………………………………………………
static Bowl b4 = new Bowl(4);
Cupboard() {
System。out。println(〃Cupboard()〃);
b4。f(2);
}
void f3(int marker) {
System。out。println(〃f3(〃 + marker + 〃)〃);
}
static Bowl b5 = new Bowl(5);
}
public class StaticInitialization {
public static void main(String'' args) {
System。out。println(
〃Creating new Cupboard() in main〃);
new Cupboard();
System。out。println(
〃Creating new Cupboard() in main〃);
new Cupboard();
t2。f2(1);
t3。f3(1);
}
static Table t2 = new Table();
static Cupboard t3 = new Cupboard();
} ///:~
Bowl 允许我们检查一个类的创建过程,而Table 和 Cupboard 能创建散布于类定义中的Bowl 的 static成
员。注意在 static定义之前,Cupboard 先创建了一个非static 的Bowl b3。它的输出结果如下:
Bowl(1)
Bowl(2)
Table()
f(1)
Bowl(4)
Bowl(5)
Bowl(3)
Cupboard()
f(2)
Creating new Cupboard() in main
Bowl(3)
Cupboard()
f(2)
Creating new Cupboard() in main
Bowl(3)
Cupboard()
f(2)
f2(1)
f3(1)
static初始化只有在必要的时候才会进行。如果不创建一个 Table 对象,而且永远都不引用Table。b1 或
Table。b2 ,那么 static Bowl b1 和b2 永远都不会创建。然而,只有在创建了第一个 Table 对象之后(或者
发生了第一次static 访问),它们才会创建。在那以后,static 对象不会重新初始化。
113
…………………………………………………………Page 115……………………………………………………………
初始化的顺序是首先 static (如果它们尚未由前一次对象创建过程初始化),接着是非static 对象。大家
可从输出结果中找到相应的证据。
在这里有必要总结一下对象的创建过程。请考虑一个名为 Dog 的类:
(1) 类型为 Dog 的一个对象首次创建时,或者Dog 类的 static方法/static 字段首次访问时,Java 解释器
必须找到Dog。class (在事先设好的类路径里搜索)。
(2) 找到Dog。class 后(它会创建一个 Class 对象,这将在后面学到),它的所有 static初始化模块都会运
行。因此,static初始化仅发生一次——在 Class 对象首次载入的时候。
(3) 创建一个new Dog()时,Dog 对象的构建进程首先会在内存堆(Heap )里为一个Dog 对象分配足够多的存
储空间。
(4) 这种存储空间会清为零,将Dog 中的所有基本类型设为它们的默认值(零用于数字,以及boolean 和
char 的等价设定)。
(5) 进行字段定义时发生的所有初始化都会执行。
(6) 执行构建器。正如第6 章将要讲到的那样,这实际可能要求进行相当多的操作,特别是在涉及继承的时
候。
3。 明确进行的静态初始化
Java 允许我们将其他static初始化工作划分到类内一个特殊的“static 构建从句”(有时也叫作“静态
块”)里。它看起来象下面这个样子:
class Spoon {
static int i;
static {
i = 47;
}
// 。 。 。
尽管看起来象个方法,但它实际只是一个 static 关键字,后面跟随一个方法主体。与其他 static初始化一
样,这段代码仅执行一次——首次生成那个类的一个对象时,或者首次访问属于那个类的一个 static 成员时
(即便从未生成过那个类的对象)。例如:
//: ExplicitStatic。java
// Explicit static initialization
// with the 〃static〃 clause。
class Cup {
Cup(int marker) {
System。out。println(〃Cup(〃 + marker + 〃)〃);
}
void f(int marker) {
System。out。println(〃f(〃 + marker + 〃)〃);
}
}
class Cups {
static Cup c1;
static Cup c2;
static {
c1 = new Cup(1);
c2 = new Cup(2);
}
Cups() {
System。out。println(〃Cups()〃);
114
…………………………………………………………Page 116……………………………………………………………
}
}
public class ExplicitStatic {
public static void main(String'' args) {
System。out。println(〃Inside main()〃);
Cups。c1。f(99); // (1)
}
static Cups x = new Cups(); // (2)
static Cups y = new Cups(); // (2)
} ///:~
在标记为(1)的行内访问 static 对象 c1 的时候,或在行(1)标记为注释,同时 (2)行不标记成注释的时候,用
于Cups 的 static初始化模块就会运行。若(1)和 (2)都被标记成注释,则用于 Cups 的static 初始化进程永
远不会发生。
4。 非静态实例的初始化
针对每个对象的非静态变量的初始化,Java 1。1 提供了一种类似的语法格式。下面是一个例子:
//: Mugs。java
// Java 1。1 〃Instance Initialization〃
class Mug {
Mug(int marker) {
System。out。println(〃Mug(〃 + marker + 〃)〃);
}
void f(int marker) {
System。out。println(〃f(〃 + marker + 〃)〃);
}
}
public class Mugs {
Mug c1;
Mug c2;
{
c1 = new Mug(1);
c2 = new Mug(2);
System。out。println(〃c1 & c2 initialized〃);
}
Mugs() {
System。out。println(〃Mugs()〃);
}
public static void main(String'' args) {
System。out。println(〃Inside main()〃);
Mugs x = new Mugs();
}
} ///:~
大家可看到实例初始化从句:
{
c1 = new Mug(1);
115
…………………………………………………………Page 117……………………………………………………………
c2 = new Mug(2);
System。out。println(〃c1 & c2 initialized〃);
}
它看起来与静态初始化从句极其相似,只是 static 关键字从里面消失了。为支持对“匿名内部类”的初始化
(参见第7 章),必须采用这一语法格式。
4。5 数组初始化
在C 中初始化数组极易出错,而且相当麻烦。C++通过“集合初始化”使其更安全(注释⑥)。Java 则没有
象C++那样的“集合”概念,因为Java 中的所有东西都是对象。但它确实有自己的数组,通过数组初始化来
提供支持。
数组代表一系列对象或者基本数据类型,所有相同的类型都封装到一起——采用一个统一的标识符名称。数
组的定义和使用是通过方括号索引运算符进行的( '')。为定义一个数组,只需在类型名后简单地跟随一对
空方括号即可:
int'' al;
也可以将方括号置于标识符后面,获得完全一致的结果:
int al'';
这种格式与 C 和 C++程序员习惯的格式是一致的。然而,最“通顺”的也许还是前一种语法,因为它指出类
型是“一个 int 数组”。本书将沿用那种格式。
编译器不允许我们告诉它一个数组有多大。这样便使我们回到了“句柄”