在上一节中类存储在文件系统的子目录中。类的路径必须与包名匹配。
另外,类文件也可以存储在 JAR(Java 归档)文件中。在一个 JAR 文件中,可以包含多个压缩形式的类文件和子目录,这样既可以节省又可以改善性能。在程序中用到第三方(third-party)的库文件时,通常会给出一个或多个需要包含的JAR文件。JDK 也提供了许多的 JAR 文件,例如,在 jre/lib/rt.jar 中包含数千个类库文件。
JAR 文件使用 ZIP 格式来组织文件和子目录。
为了使类能够被多个程序共享,需要做到下面三点:
- 把类放到一个目录中,例如 /home/user/classdir。需要注意,这个目录是树状结构的基目录(根目录)。如果希望将 com.horstmann.corejava.Employee 类添加到其中,这个 Employee.class 类文件就必须位于子目录 /home/user/classdir/com/horstmann/corejava 中。
- 将 JAR 文件放在一个目录中,例如:/home/user/archives。
- 设置类路径(class path)。类路径是所有包含类文件的路径的集合。
在UNIX环境中,类路径中的不同项目之间采用冒号(:)分隔:
/home/user/classdir:.:/home/user/archives/archive.jar
而在Windows环境中,则以分号(;)分隔:
C:\classdir;.;C:\archives\archive.jar
可以看到上述的类路径分三段:
- 基目录:
/home/user/classdir
orC:\classdir
- 当前目录:
.
- JAR 文件:
/home/user/archives/archive.jar
orC:\archives\archive.jar
从 Java SE 6 开始,可以在 JAR 文件目录中指定通配符,如:
unix like:
/home/user/classdir:.:/home/user/archives/'*'
windows:
C:\classdir;.;C:\archives\*
上述表示,在 archives 目录中的所有 JAR 文件(不包括 .class 文件)都包含在类路径中。
需要注意,在 unix like 中,注意 *
可能会导致 shell 命令进一步扩展。
由于运行时库文件(rt.jar 和在 jre/lib 与 jre/lib/ext 目录下的一些其他的 JAR 文件)会被自动地搜索,所以不必将它们显式地列在类路径中。
javac 编译器总是在当前的目录中查找文件,但 Java 虚拟机仅在类路径中有
.
目录的时候才查看当前目录。如果没有设置类路径,那也并不会产生什么问题,默认的类路径包含.
目录。然而如果设置了类路径却忘记了包含.
目录,则程序仍然可以通过编译,但不能运行。
举个例子,例如有这样一个类路径:
/home/user/classdir:.:/home/user/archives/archive.jar
假定虚拟机要搜寻 com.yikang.corejava.Employee 类文件,它首先会查看 jre/lib
和 jre/lib/ext
目录下的归档文件中所存放的系统类文件。显然,在那里找不到相应的类文件,然后再查看类路径。然后查找以下文件:
- /home/user/classdir/com/yikang/corejava/Employee.class
- ./com/yikang/corejava/Employee.class 注意是从当前目录开始
- 查看 com/yikang/corejava/Employee.java 是否在 /home/user/archives/archive.jar 中
如果是编译器定位文件,他要比虚拟机复杂得多。如果引用了一个类,而没有指出这个类所在的包,那么编译器将首先查找包含这个类的包,查找所有的 import 指令,确定其中是否包含了被引用的类。
例如,假定源文件包含指令:
import java.util.*;
import com.yikang.corejava.*;
源代码引入了 Employee 类,编译器将试图查找 java.lang.Employee
(因为 java.lang 包被默认导入)、java.util.Employee
、com.horstmann.corejava.Employee
和当前包中的Employee。在类路径中所列出的每一个类进行逐一查看。如果找到了一个以上的类,就会产生编译错误(因为类必须是唯一的,而 import 语句的次序却无关紧要)。
编译器的任务不止这些,它还要查看源文件(Source files)是否比类文件新。如果是这样的话,那么源文件就会被自动地重新编译。
在 Java 中,仅可以导入其他包中的公有类。一个源文件只能包含一个公有类,并且文件名必须与公有类匹配。因此,编译器很容易定位公有类所在的源文件。当然,也可以从当前包中导入非公有类。这些类有可能定义在与类名不同的源文件中。如果从当前包中导入一个类,编译器就要搜索当前包中的所有源文件,以便确定哪个源文件定义了这个类。
设置类路径
使用 -classpath
or -cp
选项来指定类路径:
unix like:
java -classpath /home/user/classdir:.:/home/user/archives/archive.jar MyProg
windows:
java -classpath C:\classdir;.;C:\archives\archive.jar MyProg
整个指令应该书写在一行中。将这样一个长的命令行放在一个shell脚本或一个批处理文件中是一个不错的主意。
类路径也可以通过设置 CLASSPATH 环境变量完成这个操作。其详细情况依赖于所使用的 shell。在(bash)中,命令格式如下:
export CLASSPATH=/home/user/classdir:.:/home/user/archives/archive.jar
在 windows shell(powershell) 中,命令格式如下:
set CLASSPATH=C:\classdir;.;C:\archives\archive.jar
直到退出shell为止,类路径设置均有效。
建议不要将 CLASSPATH 环境变量设置为永久不变的全局值。因为,你可以会忘记全局设置,因此,当使用的类没有正确地加载进来时,会感到很奇怪。
另外,有人建议绕开类路径,将所有的文件放在 jre/lib/ext 路径。这是一个极坏的主意,其原因主要有两个:
- 当手工地加载其他的类文件时,如果将它们存放在扩展路径上,则不能正常地工作。
- 此外,程序员经常会忘记 3 个月前所存放文件的位置。当类加载器忽略了曾经仔细设计的类路径时,程序员会毫无头绪地在头文件中查找。事实上,加载的是扩展路径上已长时间遗忘的类。