八宝书库 > 文学其他电子书 > Java编程思想第4版[中文版](PDF格式) >

第41部分

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

小说: Java编程思想第4版[中文版](PDF格式) 字数: 每页4000字

按键盘上方向键 ← 或 → 可快速上下翻页,按键盘上的 Enter 键可回到本书目录页,按键盘上方向键 ↑ 可回到本页顶部!
————未阅读完?加入书签已便下次继续阅读!




类成员的名字相互间都会隔离起来。位于类A 内的一个方法f()不会与位于类B 内的、拥有相同“签名” 

 (自变量列表)的f()发生冲突。但类名会不会冲突呢?假设创建一个stack 类,将它安装到已有一个 stack 

类(由其他人编写)的机器上,这时会出现什么情况呢?对于因特网中的 Java 应用,这种情况会在用户毫不 

知晓的时候发生,因为类会在运行一个Java 程序的时候自动下载。  

正是由于存在名字潜在的冲突,所以特别有必要对 Java 中的命名空间进行完整的控制,而且需要创建一个完 

全独一无二的名字,无论因特网存在什么样的限制。  

迄今为止,本书的大多数例子都仅存在于单个文件中,而且设计成局部(本地)使用,没有同包名发生冲突 

 (在这种情况下,类名置于“默认包”内)。这是一种有效的做法,而且考虑到问题的简化,本书剩下的部 

分也将尽可能地采用它。然而,若计划创建一个“对因特网友好”或者说“适合在因特网使用”的程序,必 

须考虑如何防止类名的重复。  

为Java 创建一个源码文件的时候,它通常叫作一个“编辑单元”(有时也叫作“翻译单元”)。每个编译单 

元都必须有一个以。java 结尾的名字。而且在编译单元的内部,可以有一个公共(public)类,它必须拥有 

与文件相同的名字(包括大小写形式,但排除。java文件扩展名)。如果不这样做,编译器就会报告出错。 

每个编译单元内都只能有一个 public 类(同样地,否则编译器会报告出错)。那个编译单元剩下的类(如果 

有的话)可在那个包外面的世界面前隐藏起来,因为它们并非“公共”的(非public),而且它们由用于主 



                                                                123 


…………………………………………………………Page 125……………………………………………………………

public 类的“支撑”类组成。  

编译一个。java 文件时,我们会获得一个名字完全相同的输出文件;但对于。java 文件中的每个类,它们都有 

一个。class 扩展名。因此,我们最终从少量的。java 文件里有可能获得数量众多的。class 文件。如以前用一 

种汇编语言写过程序,那么可能已习惯编译器先分割出一种过渡形式(通常是一个。obj 文件),再用一个链 

接器将其与其他东西封装到一起(生成一个可执行文件),或者与一个库封装到一起(生成一个库)。但那 

并不是Java 的工作方式。一个有效的程序就是一系列。class 文件,它们可以封装和压缩到一个 JAR 文件里 

 (使用Java 1。1 提供的 jar 工具)。Java 解释器负责对这些文件的寻找、装载和解释(注释①)。  

  

①:Java 并没有强制一定要使用解释器。一些固有代码的Java 编译器可生成单独的可执行文件。  

  

 “库”也由一系列类文件构成。每个文件都有一个public 类(并没强迫使用一个 public 类,但这种情况最 

很典型的),所以每个文件都有一个组件。如果想将所有这些组件(它们在各自独立的。java 和。class文件 

里)都归纳到一起,那么package 关键字就可以发挥作用)。  

若在一个文件的开头使用下述代码:  

package mypackage;  

那么package 语句必须作为文件的第一个非注释语句出现。该语句的作用是指出这个编译单元属于名为 

mypackage 的一个库的一部分。或者换句话说,它表明这个编译单元内的 public 类名位于 mypackage 这个名 

字的下面。如果其他人想使用这个名字,要么指出完整的名字,要么与mypackage 联合使用 import 关键字 

 (使用前面给出的选项)。注意根据Java 包(封装)的约定,名字内的所有字母都应小写,甚至那些中间单 

词亦要如此。  

例如,假定文件名是MyClass。java。它意味着在那个文件有一个、而且只能有一个public 类。而且那个类 

的名字必须是MyClass (包括大小写形式):  

  

package mypackage;  

public class MyClass {  

// 。 。 。  

  

现在,如果有人想使用 MyClass,或者想使用mypackage 内的其他任何public 类,他们必须用 import关键 

字激活mypackage 内的名字,使它们能够使用。另一个办法则是指定完整的名称:  

  

mypackage。MyClass m = new mypackage。MyClass();  

  

import关键字则可将其变得简洁得多:  

  

import mypackage。*;  

// 。 。 。  

MyClass m = new MyClass();  

  

作为一名库设计者,一定要记住package 和 import关键字允许我们做的事情就是分割单个全局命名空间,保 

证我们不会遇到名字的冲突——无论有多少人使用因特网,也无论多少人用Java 编写自己的类。  



5。1。1  创建独一无二的包名  



大家或许已注意到这样一个事实:由于一个包永远不会真的“封装”到单独一个文件里面,它可由多 

个。class 文件构成,所以局面可能稍微有些混乱。为避免这个问题,最合理的一种做法就是将某个特定包使 

用的所有。class 文件都置入单个目录里。也就是说,我们要利用操作系统的分级文件结构避免出现混乱局 

面。这正是 Java 所采取的方法。  

它同时也解决了另两个问题:创建独一无二的包名以及找出那些可能深藏于目录结构某处的类。正如我们在 

第2 章讲述的那样,为达到这个目的,需要将。class文件的位置路径编码到 package 的名字里。但根据约 

定,编译器强迫package 名的第一部分是类创建者的因特网域名。由于因特网域名肯定是独一无二的(由 

InterNIC 保证——注释②,它控制着域名的分配),所以假如按这一约定行事,package 的名称就肯定不会 

重复,所以永远不会遇到名称冲突的问题。换句话说,除非将自己的域名转让给其他人,而且对方也按照相 

同的路径名编写Java 代码,否则名字的冲突是永远不会出现的。当然,如果你没有自己的域名,那么必须创 



                                                                            124 


…………………………………………………………Page 126……………………………………………………………

造一个非常生僻的包名(例如自己的英文姓名),以便尽最大可能创建一个独一无二的包名。如决定发行自 

己的Java 代码,那么强烈推荐去申请自己的域名,它所需的费用是非常低廉的。  

  

②:ftp://ftp。internic  

  

这个技巧的另一部分是将package 名解析成自己机器上的一个目录。这样一来,Java 程序运行并需要装 

载。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);  

  }

返回目录 上一页 下一页 回到顶部 0 1

你可能喜欢的