友情提示:如果本网页打开太慢或显示不完整,请尝试鼠标右键“刷新”本网页!
第三电子书 返回本书目录 加入书签 我的书架 我的书签 TXT全本下载 『收藏到我的浏览器』

Java编程思想第4版[中文版](PDF格式)-第35部分

快捷操作: 按键盘上方向键 ← 或 → 可快速上下翻页 按键盘上的 Enter 键可回到本书目录页 按键盘上方向键 ↑ 可回到本页顶部! 如果本书没有阅读完,想下次继续接着阅读,可使用上方 "收藏到我的浏览器" 功能 和 "加入书签" 功能!




                                                                                             110 


…………………………………………………………Page 112……………………………………………………………

4。4。2  构建器初始化  



可考虑用构建器执行初始化进程。这样便可在编程时获得更大的灵活程度,因为我们可以在运行期调用方法 

和采取行动,从而“现场”决定初始化值。但要注意这样一件事情:不可妨碍自动初始化的进行,它在构建 

器进入之前就会发生。因此,假如使用下述代码:  

  

class Counter {  

int i;  

Counter() { i = 7; }  

// 。 。 。  

  

那么 i 首先会初始化成零,然后变成 7。对于所有基本类型以及对象句柄,这种情况都是成立的,其中包括 

在定义时已进行了明确初始化的那些一些。考虑到这个原因,编译器不会试着强迫我们在构建器任何特定的 

场所对元素进行初始化,或者在它们使用之前——初始化早已得到了保证(注释⑤)。  

  

⑤:相反,C++有自己的“构建器初始模块列表”,能在进入构建器主体之前进行初始化,而且它对于对象来 

说是强制进行的。参见《Thinking in C++》。  

  

1。 初始化顺序  

在一个类里,初始化的顺序是由变量在类内的定义顺序决定的。即使变量定义大量遍布于方法定义的中间, 

那些变量仍会在调用任何方法之前得到初始化——甚至在构建器调用之前。例如:  

  

//: OrderOfInitialization。java  

// Demonstrates initialization order。  

  

// When the constructor is called; to create a  

// Tag object; you'll see a message:  

class Tag {  

  Tag(int marker) {  

    System。out。println(〃Tag(〃 + marker + 〃)〃);  

  }  

}  

  

class Card {  

  Tag t1 = new Tag(1); // Before constructor  

  Card() {  

    // Indicate we're in the constructor:  

    System。out。println(〃Card()〃);  

    t3 = new Tag(33); // Re…initialize t3  

  }  

  Tag t2 = new Tag(2); // After constructor  

  void f() {  

    System。out。println(〃f()〃);  

  }  

  Tag t3 = new Tag(3); // At end  

}  

  

public class OrderOfInitialization {  

  public static void main(String'' args) {  

    Card t = new Card();  

    t。f(); // Shows that construction is done  

  }  



                                                                                             111 


…………………………………………………………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 数组”。本书将沿用那种格式。  

编译器不允许我们告诉它一个数组有多大。这样便使我们回到了“句柄”的问题上。此时,我们拥有的一切 

就是指向数组的一个句柄,而且尚未给数组分配任何空间。为了给数组创建相应的存储空间,必须编写一个 

初始化表达式。对于数组,初始化工作可在代码的任何地方出现,但也可以使用一种特殊的初始化表达式, 

它必须在数组创建的地方出现。这种特殊的初始化是一系列由花括号封闭起来的值。存储空间的分配(等价 

于使用new)将由编译器在这种情况下进行。例如:  

int'' a1 = { 1; 2; 3; 4; 5 };  

那么为什么还要定义一个没有数组的数组句柄呢?  

int'' a2;  

事实上在Java 中,可将一个数组分配给另一个,所以能使用下述语句:  

a2 = a1;  

我们真正准备做的是复制一个句柄,就象下面演示的那样:  

  

//: Arrays。java  

// Arrays of primitives。  

  

public class Arrays {  

  public static void main(String'' args) {  

    int'' a1 = { 1; 2; 3; 4; 5 };  

    int'' a2;  

    a2 = a1;  

    for(int i = 0; i 《 a2。length; i++)  

      a2'i'++;  

    for(int i = 0; i 《 a1。length;
返回目录 上一页 下一页 回到顶部 1 1
快捷操作: 按键盘上方向键 ← 或 → 可快速上下翻页 按键盘上的 Enter 键可回到本书目录页 按键盘上方向键 ↑ 可回到本页顶部!
温馨提示: 温看小说的同时发表评论,说出自己的看法和其它小伙伴们分享也不错哦!发表书评还可以获得积分和经验奖励,认真写原创书评 被采纳为精评可以获得大量金币、积分和经验奖励哦!