- 写在前面的话
- 引言
- 第 1 章 对象入门
- 第 2 章 一切都是对象
- 第 3 章 控制程序流程
- 第 4 章 初始化和清除
- 第 5 章 隐藏实施过程
- 第 6 章 类再生
- 第 7 章 多形性
- 第 8 章 对象的容纳
- 第 9 章 违例差错控制
- 第 10 章 Java IO 系统
- 第 11 章 运行期类型鉴定
- 第 12 章 传递和返回对象
- 第 十三 章 创建窗口和程序片
- 第 14 章 多线程
- 第 15 章 网络编程
- 第 16 章 设计范式
- 第 17 章 项目
- 附录 A 使用非 JAVA 代码
- 附录 B 对比 C++和 Java
- 附录 C Java 编程规则
- 附录 D 性能
- 附录 E 关于垃圾收集的一些话
- 附录 F 推荐读物
5.1.1 创建独一无二的包名
大家或许已注意到这样一个事实:由于一个包永远不会真的“封装”到单独一个文件里面,它可由多个.class 文件构成,所以局面可能稍微有些混乱。为避免这个问题,最合理的一种做法就是将某个特定包使用的所有.class 文件都置入单个目录里。也就是说,我们要利用操作系统的分级文件结构避免出现混乱局面。这正是 Java 所采取的方法。
它同时也解决了另两个问题:创建独一无二的包名以及找出那些可能深藏于目录结构某处的类。正如我们在第 2 章讲述的那样,为达到这个目的,需要将.class 文件的位置路径编码到 package 的名字里。但根据约定,编译器强迫 package 名的第一部分是类创建者的因特网域名。由于因特网域名肯定是独一无二的(由 InterNIC 保证——注释②,它控制着域名的分配),所以假如按这一约定行事,package 的名称就肯定不会重复,所以永远不会遇到名称冲突的问题。换句话说,除非将自己的域名转让给其他人,而且对方也按照相同的路径名编写 Java 代码,否则名字的冲突是永远不会出现的。当然,如果你没有自己的域名,那么必须创造一个非常生僻的包名(例如自己的英文姓名),以便尽最大可能创建一个独一无二的包名。如决定发行自己的 Java 代码,那么强烈推荐去申请自己的域名,它所需的费用是非常低廉的。
②:ftp://ftp.internic.net
这个技巧的另一部分是将 package 名解析成自己机器上的一个目录。这样一来,Java 程序运行并需要装载.class 文件的时候(这是动态进行的,在程序需要创建属于那个类的一个对象,或者首次访问那个类的一个 static 成员时),它就可以找到.class 文件驻留的那个目录。
Java 解释器的工作程序如下:首先,它找到环境变量 CLASSPATH(将 Java 或者具有 Java 解释能力的工具——如浏览器——安装到机器中时,通过操作系统进行设定)。CLASSPATH 包含了一个或多个目录,它们作为一种特殊的“根”使用,从这里展开对.class 文件的搜索。从那个根开始,解释器会寻找包名,并将每个点号(句点)替换成一个斜杠,从而生成从 CLASSPATH 根开始的一个路径名(所以 package foo.bar.baz 会变成 foo\bar\baz 或者 foo/bar/baz;具体是正斜杠还是反斜杠由操作系统决定)。随后将它们连接到一起,成为 CLASSPATH 内的各个条目(入口)。以后搜索.class 文件时,就可从这些地方开始查找与准备创建的类名对应的名字。此外,它也会搜索一些标准目录——这些目录与 Java 解释器驻留的地方有关。
为进一步理解这个问题,下面以我自己的域名为例,它是 bruceeckel.com。将其反转过来后,com.bruceeckel 就为我的类创建了独一无二的全局名称(com,edu,org,net 等扩展名以前在 Java 包中都是大写的,但自 Java 1.2 以来,这种情况已发生了变化。现在整个包名都是小写的)。由于决定创建一个名为 util 的库,我可以进一步地分割它,所以最后得到的包名如下:
package com.bruceeckel.util;
现在,可将这个包名作为下述两个文件的“命名空间”使用:
//: Vector.java // Creating a package package com.bruceeckel.util; public class Vector { public Vector() { System.out.println( "com.bruceeckel.util.Vector"); } } ///:~
创建自己的包时,要求 package 语句必须是文件中的第一个“非注释”代码。第二个文件表面看起来是类似的:
//: List.java // Creating a package package com.bruceeckel.util; public class List { public List() { System.out.println( "com.bruceeckel.util.List"); } } ///:~
这两个文件都置于我自己系统的一个子目录中:
C:\DOC\JavaT\com\bruceeckel\util
若通过它往回走,就会发现包名 com.bruceeckel.util,但路径的第一部分又是什么呢?这是由 CLASSPATH 环境变量决定的。在我的机器上,它是:
CLASSPATH=.;D:\JAVA\LIB;C:\DOC\JavaT
可以看出,CLASSPATH 里能包含大量备用的搜索路径。然而,使用 JAR 文件时要注意一个问题:必须将 JAR 文件的名字置于类路径里,而不仅仅是它所在的路径。所以对一个名为 grape.jar 的 JAR 文件来说,我们的类路径需要包括:
CLASSPATH=.;D:\JAVA\LIB;C:\flavors\grape.jar
正确设置好类路径后,可将下面这个文件置于任何目录里(若在执行该程序时遇到麻烦,请参见第 3 章的 3.1.2 小节“赋值”):
//: LibTest.java // Uses the library package c05; import com.bruceeckel.util.*; public class LibTest { public static void main(String[] args) { Vector v = new Vector(); List l = new List(); } } ///:~
编译器遇到 import 语句后,它会搜索由 CLASSPATH 指定的目录,查找子目录 com\bruceeckel\util,然后查找名称适当的已编译文件(对于 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 com.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 的其他东西。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论