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

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

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


  

Outer while loop  

i = 1  

continue  

i = 2  

i = 3  

continue outer  

Outer while loop  

i = 4  

i = 5  

break  

Outer while loop  

i = 6  

i = 7  

break outer  

  

大家要记住的重点是:在Java 里唯一需要用到标签的地方就是拥有嵌套循环,而且想中断或继续多个嵌套级 

别的时候。  

在Dijkstra 的“Goto 有害”论中,他最反对的就是标签,而非 goto。随着标签在一个程序里数量的增多, 

他发现产生错误的机会也越来越多。标签和goto 使我们难于对程序作静态分析。这是由于它们在程序的执行 

流程中引入了许多“怪圈”。但幸运的是,Java 标签不会造成这方面的问题,因为它们的活动场所已被限 

死,不可通过特别的方式到处传递程序的控制权。由此也引出了一个有趣的问题:通过限制语句的能力,反 

而能使一项语言特性更加有用。  



3。2。7  开关  



 “开关”(Switch)有时也被划分为一种“选择语句”。根据一个整数表达式的值,switch语句可从一系列 

代码选出一段执行。它的格式如下:  

  

switch(整数选择因子) {  

case 整数值 1 : 语句; break;  

case 整数值 2 : 语句; break;  

case 整数值 3 : 语句; break;  

case 整数值 4 : 语句; break;  

case 整数值 5 : 语句; break;  

//。。  

default:语句;  



                                                                                           91 


…………………………………………………………Page 93……………………………………………………………

}  

  

其中,“整数选择因子”是一个特殊的表达式,能产生整数值。switch 能将整数选择因子的结果与每个整数 

值比较。若发现相符的,就执行对应的语句(简单或复合语句)。若没有发现相符的,就执行default语 

句。  

在上面的定义中,大家会注意到每个case 均以一个break 结尾。这样可使执行流程跳转至 switch主体的末 

尾。这是构建 switch 语句的一种传统方式,但break 是可选的。若省略 break,会继续执行后面的case 语 

句的代码,直到遇到一个break 为止。尽管通常不想出现这种情况,但对有经验的程序员来说,也许能够善 

加利用。注意最后的default语句没有 break,因为执行流程已到了break 的跳转目的地。当然,如果考虑 

到编程风格方面的原因,完全可以在default 语句的末尾放置一个break,尽管它并没有任何实际的用处。  

switch语句是实现多路选择的一种易行方式(比如从一系列执行路径中挑选一个)。但它要求使用一个选择 

因子,并且必须是 int 或char 那样的整数值。例如,假若将一个字串或者浮点数作为选择因子使用,那么它 

们在 switch语句里是不会工作的。对于非整数类型,则必须使用一系列 if语句。  

下面这个例子可随机生成字母,并判断它们是元音还是辅音字母:  

  

//: VowelsAndConsonants。java  

// Demonstrates the switch statement  

  

public class VowelsAndConsonants {  

  public static void main(String'' args) {  

    for(int i = 0; i 《 100; i++) {  

      char c = (char)(Math。random() * 26 + 'a');  

      System。out。print(c + 〃: 〃);  

      switch(c) {  

      case 'a':  

      case 'e':  

      case 'i':  

      case 'o':  

      case 'u':  

                System。out。println(〃vowel〃);  

                break;  

      case 'y':  

      case 'w':  

                System。out。println(  

                  〃Sometimes a vowel〃);  

                break;  

      default:  

                System。out。println(〃consonant〃);  

      }  

    }  

  }  

} ///:~  

  

由于Math。random()会产生 0 到 1 之间的一个值,所以只需将其乘以想获得的最大随机数(对于英语字母, 

这个数字是 26),再加上一个偏移量,得到最小的随机数。  

尽管我们在这儿表面上要处理的是字符,但switch 语句实际使用的字符的整数值。在 case 语句中,用单引 

号封闭起来的字符也会产生整数值,以便我们进行比较。  

请注意 case 语句相互间是如何聚合在一起的,它们依次排列,为一部分特定的代码提供了多种匹配模式。也 

应注意将break 语句置于一个特定 case 的末尾,否则控制流程会简单地下移,并继续判断下一个条件是否相 

符。  

  

1。 具体的计算  



                                                                                          92 


…………………………………………………………Page 94……………………………………………………………

应特别留意下面这个语句:  

char c = (char)(Math。random() * 26 + 'a');  

Math。random()会产生一个 double值,所以 26 会转换成double 类型,以便执行乘法运算。这个运算也会产 

生一个 double 值。这意味着为了执行加法,必须无将'a'转换成一个 double。利用一个“造型”,double 结 

果会转换回 char。  

我们的第一个问题是,造型会对char 作什么样的处理呢?换言之,假设一个值是29。7,我们把它造型成一 

个 char,那么结果值到底是30 还是 29 呢?答案可从下面这个例子中得到:  

  

//: CastingNumbers。java  

// What happens when you cast a float or double  

// to an integral value?  

  

public class CastingNumbers {  

  public static void main(String'' args) {  

    double  

      above = 0。7;  

      below = 0。4;  

    System。out。println(〃above: 〃 + above);  

    System。out。println(〃below: 〃 + below);  

    System。out。println(  

      〃(int)above: 〃 + (int)above);  

    System。out。println(  

      〃(int)below: 〃 + (int)below);  

    System。out。println(  

      〃(char)('a' + above): 〃 +  

      (char)('a' + above));  

    System。out。println(  

      〃(char)('a' + below): 〃 +  

      (char)('a' + below));  

  }  

} ///:~  

  

输出结果如下:  

  

above: 0。7  

below: 0。4  

(int)above: 0  

(int)below: 0  

(char)('a' + above): a  

(char)('a' + below): a  

  

所以答案就是:将一个 float 或 double 值造型成整数值后,总是将小数部分“砍掉”,不作任何进位处理。  

第二个问题与Math。random()有关。它会产生0 和 1之间的值,但是否包括值'1'呢?用正统的数学语言表 

达,它到底是(0;1),'0;1',(0;1',还是'0;1)呢(方括号表示“包括”,圆括号表示“不包括”)?同样 

地,一个示范程序向我们揭示了答案:  

  

//: RandomBounds。java  

// Does Math。random() produce 0。0 and 1。0?  

  

public class RandomBounds {  

  static void usage() {  

    System。err。println(〃Usage: nt〃 +  



                                                                                           93 


…………………………………………………………Page 95……………………………………………………………

      〃RandomBounds lowernt〃 +  

      〃RandomBounds upper〃);  

    System。exit(1);  

  }  

  public static void main(String'' args) {  

    if(args。length != 1) usage();  

    if(args'0'。equals(〃lower〃)) {  

      while(Math。random() != 0。0)  

        ; // Keep trying  

      System。out。println(〃Produced 0。0!〃);  

    }   

    else if(args'0'。equals(〃upper〃)) {  

      while(Math。random() != 1。0)  

        ; // Keep trying  

      System。out。println(〃Produced 1。0!〃);  

    }   

    else   

      usage();  

  }  

} ///:~  

  

为运行这个程序,只需在命令行键入下述命令即可:  

java RandomBounds lower  

或  

java RandomBounds upper  

  

在这两种情况下,我们都必须人工中断程序,所以会发现 Math。random() “似乎”永远都不会产生0。0 或 

1。0。但这只是一项实验而已。若想到 0 和 1 之间有 2 的 128次方不同的双精度小数,所以如果全部产生这些 

数字,花费的时间会远远超过一个人的生命。当然,最后的结果是在Math。random() 的输出中包括了0。0。或 

者用数字语言表达,输出值范围是 '0;1)。  



3。3 总结  



本章总结了大多数程序设计语言都具有的基本特性:计算、运算符优先顺序、类型转换以及选择和循环等 

等。现在,我们作好了相应的准备,可继续向面向对象的程序设计领域迈进。在下一章里,我们将讨论对象 

的初始化与清除问题,再后面则讲述隐藏的基本实现方法。  



3。4 练习  



(1) 写一个程序,打印出 1 到 100 间的整数。  

(2) 修改练习(1),在值为47 时用一个break 退出程序。亦可换成 return 试试。  

(3) 创建一个 switch 语句,为每一种case 都显示一条消息。并将 switch置入一个 for 循环里,令其尝试每 

一种case。在每个case 后面都放置一个break,并对其进行测试。然后,删除break,看看会有什么情况出 

现。  



                                                                                          94 


…………………………………………………………Page 96……………………………………………………………

                             第 4 章  初始化和清除  



  

 “随着计算机的进步,‘不安全’的程序设计已成为造成编程代价高昂的罪魁祸首之一。”  

  

 “初始化”和“清除”是这些安全问题的其中两个。许多C 程序的错误都是由于程序员忘记初始化一个变量 

造成的。对于现成的库,若用户不知道如何初始化库的一个组件,就往往会出现这一类的错误。清除是另一 

个特殊的问题,因为用完一个元素后,由于不再关心,所以很容易把它忘记。这样一来,那个元素占用的资 

源会一直保留下去,极易产生资源(主要是内存)用尽的后果。  

C++为我们引入了“构建器”的概念。这是一种特殊的方法,在一个对象创建之后自动调用。Java 也沿用了 

这个概念,但新增了自己的“垃圾收集器”,能在资源不再需要的时候自动释放它们。本章将讨论初始化和 

清除的问题,以及 Java 如何提供它们的支持。  



4。1 用构建器自动初始化  



对于方法的创建,可将其想象成为自己写的每个类都调用一次 initialize()。这个名字提醒我们在使用对象 

之前,应首先进行这样的调用。但不幸的是,这也意味着用户必须记住调用方法。在 Java 中,由于提供了名 

为“构建器”的一种特殊方法,所以类的设计者可担保每个对象都会得到正确的初始化。若某个类有一个构 

建器,那么在创建对象时,Java 会自动调用那个构建器——甚至在用户毫不知觉的情况下。所以说这是可以 

担保的!  

接着的一个问题是如何命名这个方法。存在两方面的问题。第一个是我们使用的任何名字都可能与打算为某 

个类成员使用的名字冲突。第二是由于编译器的责任是调用构建器,所以它必须知道要调用是哪个方法。C++ 

采取的方案看来是最简单的,且更有逻辑性,所以也在Java 里得到了应用:构建器的名字与类名相同。这样 

一来,可保证象这样的一个方法会在初始化期间自动调用。  

下面是带有构建器的一个简单的类(若执行这个程序有问题,请参考第3 章的“赋值”小节)。  

  

//: SimpleConstructor。java  

// Demonstration of a simple constructor  

package c04;  

  

class Rock {  

  Rock() { // This is the constructor  

    System。out。println(〃Creating Rock〃);  

  }  

}  

  

public class SimpleConstructor {  

  public static void main(String'' args) {  

    for(int i = 0; i 《 10; i++)  

      new Rock();  

  }  

} ///:~  

  

现在,一旦创建一个对象:  

new Rock();  

就会分配相应的存储空间,并调用构建器。这样可保证在我们经手之前,对象得到正确的初始化。  

请注意所有方法首字母小写的编码规则并不适用于构建器。这是由于构建器的名字必须与类名完全相同!  

和其他任何方法一样,构建器也能使用自变量,以便我们指定对象的具体创建方式。可非常方便地改动上述 

例子,以便构建器使用自己的自变量。如下所示:  

  

class Rock {  

  Rock(int i) {  



                                                                                  95 


…………………………………………………………Page 97……………………………………………………………

    System。out。println(  

      〃Creating Rock number 〃 + i);  

  }  

}  

  

public class SimpleConstructor {  

  public static void main(String'' args) {  

    for(int i = 0; i 《 10; i++)  

      new Rock(i);  

  }  

}  

  

利用构建器的自变量,我们可为一个对象的初始化设定相应的参数。举个例子来说,假设类 Tree 有一个构建 

器,它用一个整数自变量标记树的高度,那么就可以象下面这样创建一个 Tree 对象:  

  

tree t = new Tree(12); // 12 英尺高的树  

  

若Tree(int)是我们唯一的构建器,那么编译器不会允许我们以其他任何方式创建一个 Tree 对象。  

构建器有助于消除大量涉及类的问题,并使代码更易阅读。例如在前述的代码段中,我们并未看到对 

initialize()方法的明确调用——那些方法在概念上独立于定义内容。在Java 中,定义和初始化属于统一的 

概念——两者缺一不可。  

构建器属于一种较特殊的方法类型,因为它没有返回值。这与 void 返回值存在着明显的区别。对于void 返 

回值,尽管方法本身不会自动返回什么,但仍然可以让它返回另一些东西。构建器则不同,它不仅什么也不 

会自动返回,而且根本不能有任何选择。若存在一个返回值,而且假设我们可以自行选择返回内容,那么编 

译器多少要知道如何对那个返回值作什么样的处理。  



4。2 方法过载  



在任何程序设计语言中,一项重要的特性就是名字的运用。我们创建一个对象时,会分配到一个保存区域的 

名字。方法名代表的是一种具体的行动。通过用名字描述自己的系统,可使自己的程序更易人们理解和修 

改。它非常象写散文——目的是与读者沟通。  

我们用名字引用或描述所有对象与方法。若名字选得好,可使自己及其他人更易理解自己的代码。  

将人类语言中存在细致差别的概念“映射”到一种程序设计语言中时,会出现一些特殊的问题。在日常生活 

中,我们用相同的词表达多种不同的含义——即词的“过载”。我们说“洗衬衫”、“洗车”以及“洗 

狗”。但若强制象下面这样说,就显得很愚蠢:“衬衫洗 衬衫”、“车洗 车”以及“狗洗 狗”。这是由于 

听众根本不需要对执行的行动作任何明确的区分。人类的大多数语言都具有很强的“冗余”性,所以即使漏 

掉了几个词,仍然可以推断出含义。我们不需要独一无二的标识符——可从具体的语境中推论出含义。  

大多数程序设计语言(特别是 C)要求我们为每个函数都设定一个独一无二的标识符。所以绝对不能用一个 

名为print()的函数来显示整数,再用另一个print()显示浮点数——每个函数都要求具备唯一的名字。  

在Java 里,另一项因素强迫方法名出现过载情况:构建器。由于构建器的名字由类名决定,所以只能有一个 

构建器名称。但假若我们想用多种方式创建一个对象呢?例如,假设我们想创建一个类,令其用标准方式进 

行初始化,另外从文件里读取信息来初始化。此时,我们需要两个构建器,一个没有自变量(默认构建 

器),另一个将字串作为自变量——用于初始化对象的那个文件的名字。由于都是构建器,所以它们必须有 

相同的名字,亦即类名。所以为了让相同的方法名伴随不同的自变量类型使用,“方法过载”是非常关键的 

一项措施。同时,尽管方法过载是构建器必需的,但它亦可应用于其他任何方法,且用法非常方便。  

在下面这个例子里,我们向大家同时展示了过载构建器和过载的原始方法:  

  

//: Overloading。java  

// Demonstration of both constructor  

// and ordinary method overloading。  

import java。util。*;  

  

class Tree {  



                                                                               96 


…………………………………………………………Page 98……………………………………………………………

  int height;  

  Tree() {  

    prt(〃Planting a seedling〃);  

    height = 0;  

  }  

  Tree(int i) {  

    prt(〃Creating new Tree that is 〃  

        + i + 〃 feet tall〃);  

    height = i;  

  }  

  void info() {  

    prt(〃Tree is 〃 + height  

        + 〃 feet tall〃);  

  }  

  void info(String s) {  

    prt(s + 〃: Tree is 〃  

        + height + 〃 feet tall〃);  

  }  

  static void prt(String s) {  

    System。out。println(s);  

  }  

}  

  

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〃);  

    }  

    // Overloaded constructor:  

    new Tree();  

  }  

} ///:~  

  

Tree 既可创建成一颗种子,不含任何自变量;亦可创建成生长在苗圃中的植物。为支持这种创建,共使用了 

两个构建器,一个没有自变量(我们把没有自变量的构建器称作“默认构建器”,注释①),另一个采用现 

成的高度。  

  

①:在 Sun 公司出版的一些Java 资料中,用简陋但很说明问题的词语称呼这类构建器—— “无参数构建器” 

 (no…arg constructors)。但“默认构建器”这个称呼已使用了许多年,所以我选择了它。  

  

我们也有可能希望通过多种途径调用 info()方法。例如,假设我们有一条额外的消息想显示出来,就使用 

String 自变量;而假设没有其他话可说,就不使用。由于为显然相同的概念赋予了两个独立的名字,所以看 

起来可能有些古怪。幸运的是,方法过载允许我们为两者使用相同的名字。  



4。2。1  区分过载方法  



若方法有同样的名字,Java 怎样知道我们指的哪一个方法呢?这里有一个简单的规则:每个过载的方法都必 

须采取独一无二的自变量类型列表。  

若稍微思考几秒钟,就会想到这样一个问题:除根据自变量的类型,程序员如何区分两个同名方法的差异 

呢?  

即使自变量的顺序也足够我们区分两个
返回目录 上一页 下一页 回到顶部 10 9
快捷操作: 按键盘上方向键 ← 或 → 可快速上下翻页 按键盘上的 Enter 键可回到本书目录页 按键盘上方向键 ↑ 可回到本页顶部!
温馨提示: 温看小说的同时发表评论,说出自己的看法和其它小伙伴们分享也不错哦!发表书评还可以获得积分和经验奖励,认真写原创书评 被采纳为精评可以获得大量金币、积分和经验奖励哦!