在上一节中类存储在文件系统的子目录中。类的路径必须与包名匹配。
另外,类文件也可以存储在 JAR(Java 归档)文件中。在一个 JAR 文件中,可以包含多个压缩形式的类文件和子目录,这样既可以节省又可以改善性能。在程序中用到第三方(third-party)的库文件时,通常会给出一个或多个需要包含的JAR文件。JDK 也提供了许多的 JAR 文件,例如,在 jre/lib/rt.jar 中包含数千个类库文件。

JAR 文件使用 ZIP 格式来组织文件和子目录。

为了使类能够被多个程序共享,需要做到下面三点:

  1. 把类放到一个目录中,例如 /home/user/classdir。需要注意,这个目录是树状结构的基目录(根目录)。如果希望将 com.horstmann.corejava.Employee 类添加到其中,这个 Employee.class 类文件就必须位于子目录 /home/user/classdir/com/horstmann/corejava 中。
  2. 将 JAR 文件放在一个目录中,例如:/home/user/archives。
  3. 设置类路径(class path)。类路径是所有包含类文件的路径的集合。

在UNIX环境中,类路径中的不同项目之间采用冒号(:)分隔:

  1. /home/user/classdir:.:/home/user/archives/archive.jar

而在Windows环境中,则以分号(;)分隔:

  1. C:\classdir;.;C:\archives\archive.jar

可以看到上述的类路径分三段:

  • 基目录:/home/user/classdir or C:\classdir
  • 当前目录:.
  • JAR 文件:/home/user/archives/archive.jar or C:\archives\archive.jar

从 Java SE 6 开始,可以在 JAR 文件目录中指定通配符,如:

  1. unix like:
  2. /home/user/classdir:.:/home/user/archives/'*'
  3. windows:
  4. C:\classdir;.;C:\archives\*

上述表示,在 archives 目录中的所有 JAR 文件(不包括 .class 文件)都包含在类路径中。
需要注意,在 unix like 中,注意 * 可能会导致 shell 命令进一步扩展。
由于运行时库文件(rt.jar 和在 jre/lib 与 jre/lib/ext 目录下的一些其他的 JAR 文件)会被自动地搜索,所以不必将它们显式地列在类路径中。

javac 编译器总是在当前的目录中查找文件,但 Java 虚拟机仅在类路径中有 . 目录的时候才查看当前目录。如果没有设置类路径,那也并不会产生什么问题,默认的类路径包含 . 目录。然而如果设置了类路径却忘记了包含 . 目录,则程序仍然可以通过编译,但不能运行。

举个例子,例如有这样一个类路径:

  1. /home/user/classdir:.:/home/user/archives/archive.jar

假定虚拟机要搜寻 com.yikang.corejava.Employee 类文件,它首先会查看 jre/libjre/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 指令,确定其中是否包含了被引用的类。
例如,假定源文件包含指令:

  1. import java.util.*;
  2. import com.yikang.corejava.*;

源代码引入了 Employee 类,编译器将试图查找 java.lang.Employee(因为 java.lang 包被默认导入)、java.util.Employeecom.horstmann.corejava.Employee 和当前包中的Employee。在类路径中所列出的每一个类进行逐一查看。如果找到了一个以上的类,就会产生编译错误(因为类必须是唯一的,而 import 语句的次序却无关紧要)。
编译器的任务不止这些,它还要查看源文件(Source files)是否比类文件新。如果是这样的话,那么源文件就会被自动地重新编译。
在 Java 中,仅可以导入其他包中的公有类。一个源文件只能包含一个公有类,并且文件名必须与公有类匹配。因此,编译器很容易定位公有类所在的源文件。当然,也可以从当前包中导入非公有类。这些类有可能定义在与类名不同的源文件中。如果从当前包中导入一个类,编译器就要搜索当前包中的所有源文件,以便确定哪个源文件定义了这个类。

设置类路径

使用 -classpath or -cp 选项来指定类路径:

  1. unix like:
  2. java -classpath /home/user/classdir:.:/home/user/archives/archive.jar MyProg
  3. windows:
  4. java -classpath C:\classdir;.;C:\archives\archive.jar MyProg

整个指令应该书写在一行中。将这样一个长的命令行放在一个shell脚本或一个批处理文件中是一个不错的主意。
类路径也可以通过设置 CLASSPATH 环境变量完成这个操作。其详细情况依赖于所使用的 shell。在(bash)中,命令格式如下:

  1. export CLASSPATH=/home/user/classdir:.:/home/user/archives/archive.jar

在 windows shell(powershell) 中,命令格式如下:

  1. set CLASSPATH=C:\classdir;.;C:\archives\archive.jar

直到退出shell为止,类路径设置均有效。

建议不要将 CLASSPATH 环境变量设置为永久不变的全局值。因为,你可以会忘记全局设置,因此,当使用的类没有正确地加载进来时,会感到很奇怪。

另外,有人建议绕开类路径,将所有的文件放在 jre/lib/ext 路径。这是一个极坏的主意,其原因主要有两个:

  • 当手工地加载其他的类文件时,如果将它们存放在扩展路径上,则不能正常地工作。
  • 此外,程序员经常会忘记 3 个月前所存放文件的位置。当类加载器忽略了曾经仔细设计的类路径时,程序员会毫无头绪地在头文件中查找。事实上,加载的是扩展路径上已长时间遗忘的类。