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

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

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



载。class 文件的时候(这是动态进行的,在程序需要创建属于那个类的一个对象,或者首次访问那个类的一 

个 static 成员时),它就可以找到。class 文件驻留的那个目录。  

Java 解释器的工作程序如下:首先,它找到环境变量CLASSPATH (将Java 或者具有 Java 解释能力的工具— 

—如浏览器——安装到机器中时,通过操作系统进行设定)。CLASSPATH 包含了一个或多个目录,它们作为 

一种特殊的“根”使用,从这里展开对。class文件的搜索。从那个根开始,解释器会寻找包名,并将每个点 

号(句点)替换成一个斜杠,从而生成从CLASSPATH 根开始的一个路径名(所以package foo。bar。baz 会变 

成foobarbaz 或者foo/bar/baz;具体是正斜杠还是反斜杠由操作系统决定)。随后将它们连接到一起, 

成为CLASSPATH 内的各个条目(入口)。以后搜索。class文件时,就可从这些地方开始查找与准备创建的类 

名对应的名字。此外,它也会搜索一些标准目录——这些目录与 Java 解释器驻留的地方有关。  

为进一步理解这个问题,下面以我自己的域名为例,它是 bruceeckel。。将其反转过来后, 

。bruceeckel 就为我的类创建了独一无二的全局名称(,edu,org,net 等扩展名以前在Java 包中都 

是大写的,但自Java 1。2 以来,这种情况已发生了变化。现在整个包名都是小写的)。由于决定创建一个名 

为util 的库,我可以进一步地分割它,所以最后得到的包名如下:  

package 。bruceeckel。util;  

现在,可将这个包名作为下述两个文件的“命名空间”使用:  

  

//: Vector。java  

// Creating a package  

package 。bruceeckel。util;  

  

public class Vector {  

  public Vector() {  

    System。out。println(  

      〃。bruceeckel。util。Vector〃);  

  }  

} ///:~  

  

创建自己的包时,要求 package 语句必须是文件中的第一个“非注释”代码。第二个文件表面看起来是类似 

的:  

  

//: List。java  

// Creating a package   

package 。bruceeckel。util;  

  

public class List {  

  public List() {  

    System。out。println(  

      〃。bruceeckel。util。List〃);  

  }  

} ///:~  

  

这两个文件都置于我自己系统的一个子目录中:  

C:DOCJavaTbruceeckelutil  

若通过它往回走,就会发现包名。bruceeckel。util,但路径的第一部分又是什么呢?这是由CLASSPATH 

环境变量决定的。在我的机器上,它是:  

CLASSPATH=。;D:JAVA LIB;C:DOCJavaT  



                                                                                        125 


…………………………………………………………Page 127……………………………………………………………

可以看出,CLASSPATH 里能包含大量备用的搜索路径。然而,使用 JAR 文件时要注意一个问题:必须将JAR 

文件的名字置于类路径里,而不仅仅是它所在的路径。所以对一个名为grape。jar 的JAR 文件来说,我们的 

类路径需要包括:  

CLASSPATH=。;D:JAVA LIB;C:flavorsgrape。jar  

正确设置好类路径后,可将下面这个文件置于任何目录里(若在执行该程序时遇到麻烦,请参见第3 章的 

3。1。2 小节“赋值”):  

  

//: LibTest。java  

// Uses the library  

package c05;  

import 。bruceeckel。util。*;  

  

public class LibTest {  

  public static void main(String'' args) {  

    Vector v = new Vector();  

    List l = new List();  

  }  

} ///:~  

  

编译器遇到 import语句后,它会搜索由CLASSPATH 指定的目录,查找子目录 bruceeckelutil,然后查 

找名称适当的已编译文件(对于Vector 是Vector。class,对于 List 则是List。class)。注意Vector 和 

List 内无论类还是需要的方法都必须设为public。  

  

1。 自动编译  

为导入的类首次创建一个对象时(或者访问一个类的static 成员时),编译器会在适当的目录里寻找同名 

的。class 文件(所以如果创建类 X 的一个对象,就应该是 X。class)。若只发现X。class,它就是必须使用 

的那一个类。然而,如果它在相同的目录中还发现了一个 X。java,编译器就会比较两个文件的日期标记。如 

果X。java 比X。class 新,就会自动编译 X。java,生成一个最新的 X。class。  

对于一个特定的类,或在与它同名的。java 文件中没有找到它,就会对那个类采取上述的处理。  

  

2。 冲突  

若通过*导入了两个库,而且它们包括相同的名字,这时会出现什么情况呢?例如,假定一个程序使用了下述 

导入语句:  

import 。bruceeckel。util。*;  

import java。util。*;  

由于java。util。*也包含了一个Vector 类,所以这会造成潜在的冲突。然而,只要冲突并不真的发生,那么 

就不会产生任何问题——这当然是最理想的情况,因为否则的话,就需要进行大量编程工作,防范那些可能 

可能永远也不会发生的冲突。  

如现在试着生成一个Vector,就肯定会发生冲突。如下所示:  

Vector v = new Vector();  

它引用的到底是哪个Vector 类呢?编译器对这个问题没有答案,读者也不可能知道。所以编译器会报告一个 

错误,强迫我们进行明确的说明。例如,假设我想使用标准的 Java Vector,那么必须象下面这样编程:  

java。util。Vector v = new java。util。Vector();  

由于它(与CLASSPATH 一起)完整指定了那个Vector 的位置,所以不再需要 import java。util。*语句,除 

非还想使用来自java。util 的其他东西。  



5。1。2  自定义工具库  



掌握前述的知识后,接下来就可以开始创建自己的工具库,以便减少或者完全消除重复的代码。例如,可为 

System。out。println()创建一个别名,减少重复键入的代码量。它可以是名为 tools 的一个包(package)的 

一部分:  

  

//: P。java  



                                                                                   126 


…………………………………………………………Page 128……………………………………………………………

// The P。rint & P。rintln shorthand  

package 。bruceeckel。tools;  

  

public class P {  

  public static void rint(Object obj) {  

    System。out。print(obj);  

  }  

  public static void rint(String s) {  

    System。out。print(s);  

  }  

  public static void rint(char'' s) {  

    System。out。print(s);  

  }  

  public static void rint(char c) {  

    System。out。print(c);  

  }  

  public static void rint(int i) {  

    System。out。print(i);  

  }  

  public static void rint(long l) {  

    System。out。print(l);  

  }  

  public static void rint(float f) {  

    System。out。print(f);  

  }  

  public static void rint(double d) {  

    System。out。print(d);  

  }  

  public static void rint(boolean b) {  

    System。out。print(b);  

  }  

  public static void rintln() {  

    System。out。println();  

  }  

  public static void rintln(Object obj) {  

    System。out。println(obj);  

  }  

  public static void rintln(String s) {  

    System。out。println(s);  

  }  

  public static void rintln(char'' s) {  

    System。out。println(s);  

  }  

  public static void rintln(char c) {  

    System。out。println(c);  

  }  

  public static void rintln(int i) {  

    System。out。println(i);  

  }  

  public static void rintln(long l) {  

    System。out。println(l);  

  }  



                                                                                          127 


…………………………………………………………Page 129……………………………………………………………

  public static void rintln(float f) {  

    System。out。println(f);  

  }  

  public static void rintln(double d) {  

    System。out。println(d);  

  }  

  public static void rintln (boolean b) {  

    System。out。println(b);  

  }  

} ///:~  

  

所有不同的数据类型现在都可以在一个新行输出(P。rintln()),或者不在一个新行输出(P。rint() )。  

大家可能会猜想这个文件所在的目录必须从某个CLASSPATH 位置开始,然后继续/bruceeckel/tools。编 

译完毕后,利用一个 import 语句,即可在自己系统的任何地方使用P。class 文件。如下所示:  

  

//: ToolTest。java  

// Uses the tools library  

import 。bruceeckel。tools。*;  

  

public class ToolTest {  

  public static void main(String'' args) {  

    P。rintln(〃Available from now on!〃);  

  }  

} ///:~  

  

所以从现在开始,无论什么时候只要做出了一个有用的新工具,就可将其加入tools 目录(或者自己的个人 

util 或tools 目录)。  

  

1。 CLASSPATH 的陷阱  

P。java 文件存在一个非常有趣的陷阱。特别是对于早期的 Java 实现方案来说,类路径的正确设定通常都是 

很困难的一项工作。编写这本书的时候,我引入了 P。java 文件,它最初看起来似乎工作很正常。但在某些情 

况下,却开始出现中断。在很长的时间里,我都确信这是 Java 或其他什么在实现时一个错误。但最后,我终 

于发现在一个地方引入了一个程序(即第 17 章要说明的 CodePackager。java),它使用了一个不同的类P。 

由于它作为一个工具使用,所以有时候会进入类路径里;另一些时候则不会这样。但只要它进入类路径,那 

么假若执行的程序需要寻找。bruceeckel。tools 中的类,Java 首先发现的就是CodePackager。java 中的 

P。此时,编译器会报告一个特定的方法没有找到。这当然是非常令人头疼的,因为我们在前面的类 P 里明明 

看到了这个方法,而且根本没有更多的诊断报告可为我们提供一条线索,让我们知道找到的是一个完全不同 

的类(那甚至不是public 的)。  

乍一看来,这似乎是编译器的一个错误,但假若考察 import 语句,就会发现它只是说:“在这里可能发现了 

P”。然而,我们假定的是编译器搜索自己类路径的任何地方,所以一旦它发现一个P,就会使用它;若在搜 

索过程中发现了“错误的”一个,它就会停止搜索。这与我们在前面表述的稍微有些区别,因为存在一些讨 

厌的类,它们都位于包内。而这里有一个不在包内的P,但仍可在常规的类路径搜索过程中找到。  

如果您遇到象这样的情况,请务必保证对于类路径的每个地方,每个名字都仅存在一个类。  



5。1。3  利用导入改变行为  



Java 已取消的一种特性是C 的“条件编译”,它允许我们改变参数,获得不同的行为,同时不改变其他任何 

代码。Java 之所以抛弃了这一特性,可能是由于该特性经常在 C 里用于解决跨平台问题:代码的不同部分根 

据具体的平台进行编译,否则不能在特定的平台上运行。由于 Java 的设计思想是成为一种自动跨平台的语 

言,所以这种特性是没有必要的。  

然而,条件编译还有另一些非常有价值的用途。一种很常见的用途就是调试代码。调试特性可在开发过程中 

使用,但在发行的产品中却无此功能。Alen Holub (holub。 )提出了利用包(package)来模仿条件 

编译的概念。根据这一概念,它创建了C “断定机制”一个非常有用的Java 版本。之所以叫作“断定机 



                                                                                   128 


…………………………………………………………Page 130……………………………………………………………

制”,是由于我们可以说“它应该为真”或者“它应该为假”。如果语句不同意你的断定,就可以发现相关 

的情况。这种工具在调试过程中是特别有用的。  

可用下面这个类进行程序调试:  

  

//: Assert。java  

// Assertion tool for debugging  

package 。bruceeckel。tools。debug;  

  

public class Assert {  

  private static void perr(String msg) {  

    System。err。println(msg);  

  }  

  public final static void is_true(boolean exp) {  

    if(!exp) perr(〃Assertion failed〃);  

  }  

  public final static void is_false(boolean exp){  

    if(exp) perr(〃Assertion failed〃);  

  }  

  public final static void   

  is_true(boolean exp; String msg) {  

    if(!exp) perr(〃Assertion failed: 〃 + msg);  

  }  

  public final static void   

  is_false(boolean exp; String msg) {  

    if(exp) perr(〃Assertion failed: 〃 + msg);  

  }  

} ///:~  

  

这个类只是简单地封装了布尔测试。如果失败,就显示出出错消息。在第 9 章,大家还会学习一个更高级的 

错误控制工具,名为“违例控制”。但在目前这种情况下,perr()方法已经可以很好地工作。  

如果想使用这个类,可在自己的程序中加入下面这一行:  

import 。bruceeckel。tools。debug。*;  

如欲清除断定机制,以便自己能发行最终的代码,我们创建了第二个Assert 类,但却是在一个不同的包里:  

  

//: Assert。java  

// Turning off the assertion output   

// so you can ship the program。  

package 。bruceeckel。tools;  

  

public class Assert {  

  public final static void is_true(boolean exp){}  

  public final static void is_false(boolean exp){}  

  public final static void   

  is_true(boolean exp; String msg) {}  

  public final static void   

  is_false(boolean exp; String msg) {}  

} ///:~  

  

现在,假如将前一个 import 语句变成下面这个样子:  

import 。bruceeckel。tools。*;  

程序便不再显示出断言。下面是个例子:  



                                                                                          129 


…………………………………………………………Page 131……………………………………………………………

  

//: TestAssert。java  

// Demonstrating the assertion tool  

package c05;  

// ment the following; and unment the  

// subsequent line to change assertion behavior:  

import 。bruceeckel。tools。debug。*;  

// import 。bruceeckel。tools。*;  

  

public class TestAssert {  

  public static void main(String'' args) {  

    Assert。is_true((2 + 2) == 5);  

    Assert。is_false((1 + 1) == 2);  

    Assert。is_true((2 + 2) == 5; 〃2 + 2 == 5〃);  

    Assert。is_false((1 + 1) == 2; 〃1 +1 != 2〃);  

  }  

} ///:~  

  

通过改变导入的package,我们可将自己的代码从调试版本变成最终的发行版本。这种技术可应用于任何种 

类的条件代码。  



5。1。4  包的停用  



大家应注意这样一个问题:每次创建一个包后,都在为包取名时间接地指定了一个目录结构。这个包必须存 

在(驻留)于由它的名字规定的目录内。而且这个目录必须能从CLASSPATH 开始搜索并发现。最开始的时 

候,package 关键字的运用可能会令人迷惑,因为除非坚持遵守根据目录路径指定包名的规则,否则就会在 

运行期获得大量莫名其妙的消息,指出找不到一个特定的类——即使那个类明明就在相同的目录中。若得到 

象这样的一条消息,请试着将package 语句作为注释标记出去。如果这样做行得通,就可知道问题到底出在 

哪儿。  



5。2 Java 访问指示符  



针对类内每个成员的每个定义,Java 访问指示符 poublic,protected 以及private 都置于它们的最前面— 

—无论它们是一个数据成员,还是一个方法。每个访问指示符都只控制着对那个特定定义的访问。这与C++ 

存在着显著不同。在C++中,访问指示符控制着它后面的所有定义,直到又一个访问指示符加入为止。  

通过千丝万缕的联系,程序为所有东西都指定了某种形式的访问。在后面的小节里,大家要学习与各类访问 

有关的所有知识。首次从默认访问开始。  



5。2。1  “友好的”  



如果根本不指定访问指示符,就象本章之前的所有例子那样,这时会出现什么情况呢?默认的访问没有关键 

字,但它通常称为“友好”(Friendly )访问。这意味着当前包内的其他所有类都能访问“友好的”成员, 

但对包外的所有类来说,这些成员却是“私有”(Private)的,外界不得访问。由于一个编译单元(一个文 

件)只能从属于单个包,所以单个编译单元内的所有类相互间都是自动“友好”的。因此,我们也说友好元 

素拥有“包访问”权限。  

友好访问允许我们将相关的类都组合到一个包里,使它们相互间方便地进行沟通。将类组合到一个包内以后 

 (这样便允许友好成员的相互访问,亦即让它们“交朋友”),我们便“拥有”了那个包内的代码。只有我 

们已经拥有的代码才能友好地访问自己拥有的其他代码。我们可认为友好访问使类在一个包内的组合显得有 

意义,或者说前者是后者的原因。在许多语言中,我们在文件内组织定义的方式往往显得有些牵强。但在 

Java 中,却强制用一种颇有意义的形式进行组织。除此以外,我们有时可能想排除一些类,不想让它们访问 

当前包内定义的类。  

对于任何关系,一个非常重要的问题是“谁能访问我们的‘私有’或private 代码”。类控制着哪些代码能 

够访问自己的成员。没有任何秘诀可以“闯入”。另一个包内推荐可以声明一个新类,然后说:“嗨,我是 

Bob 的朋友!”,并指望看到Bob 的“protected”(受到保护的)、友好的以及“private”(私有)的成 



                                                                            130 


…………………………………………………………Page 132……………………………………………………………

员。为获得对一个访问权限,唯一的方法就是:  

(1) 使成员成为“public”(公共的)。这样所有人从任何地方都可以访问它。  

(2) 变成一个“友好”成员,方法是舍弃所有访问指示符,并将其类置于相同的包内。这样一来,其他类就 

可以访问成员。  

(3) 正如以后引入“继承”概念后大家会知道的那样,一个继承的类既可以访问一个 protected 成员,也可 

以访问一个public 成员(但不可访问 private 成员)。只有在两个类位于相同的包内时,它才可以访问友好 

成员。但现在不必关心这方面的问题。  

(4) 提供“访问器/变化器”方法(亦称为“获取/设置”方法),以便读取和修改值。这是OOP 环境中最 

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