在前面已经看到,类存储在文件系统的子目录中。类的路径必须与包名匹配。
另外,类文件也可以存储在JAR(Java归档)文件中。在一个JAR文件中,可以包含多个压缩形式的类文件和子目录,这样既可以节省又可以改善性能。在程序中用到第三方(third-party)的库文件时,通常会给出一个或多个需要包含的JAR文件。JDK也提供了许多的JAR文件,例如,在jre/lib子目录下包含数千个类库文件。有关创建JAR文件的详细内容将在第10章中讨论。
提示:JAR文件使用ZIP格式组织文件和子目录。可以使用所有ZIP实用程序查看内部的rt.jar以及其他的JAR文件。
为了使类能够被多个程序共享,需要做到下面几点:
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环境中,类路径中的不同项目之间采用冒号(:)分隔:
/home/user/classdir:.:/home/user/archives/archive.jar
而在Windows环境中,则以分号(;)分隔:
c:\clasdir;,;c:\archives\archive.jar
在上述两种情况中,句点(.)表示当前目录。
类路径包括:
- 基目录/home/user/classdir或c:\classes;
- 当前目录(.);
- JAR文件/home/user/archives/archive.jar或c:\archives\archive.jar
从Java SE 6开始,可以在JAR文件目录中指定通配符,如下:
/home/user/classdir:.:/home/user/archives/'*'
或
c:\classdir;.;c:\archives\*
但在UNIX中,禁止使用*以防止shell命令进一步扩展。
在归档名录中的所有JAR文件(但不包括.class文件)都包含在类路径中。
由于运行时库文件(rt.jar和在jre/lib与jre/lit/ext目录下的一些其他的JAR文件)会自动地搜索,所以不必将它们显式地列在类路径中。
警告:javac编译器总是在当前的目录中查找文件,但Java虚拟机仅在类路径中有“.”目录的时候才查看当前目录。如果没有类设置类路径,那也并不会产生什么问题,默认的类路径包含“.”目录。然而如果设置了类路径却忘记了包含“.”目录,则程序仍然可以通过编译,但不能运行。
类路径所列出的目录和归档文件是搜寻类的起始点。下面看一个类路径实例:
/home/user/classdir:.:/home/user/archives/archive.jar
假定虚拟机要搜寻com.horstmann.corejava.Employee类文件。它首先要查看存储在jre/lib和jre/lib/ext目录下的归档文件中所存放的系统文件类。显然,在那里找不到相应的类文件,然后再查看类路径。于是查看:
/home/user/classdir/com/horstmann/corejava/Employee.class
com/horstmann/corejava/Employee.class从当前目录开始
com/horstmann/corejava/Employee.class inside /home/user/archives/archive.jar
编译器定位文件要比虚拟机复杂的多。如果引用了一个类,而没有指出这个类所在的包,那么编译器将首先查找包含这个类的包,并查询所有的import指令,确定其中是否包含了被引用的类。例如,假定源文件包含指令:
import java.util.*;
import com.horstmann.corejava.*;
并且源代码引用了Employee类。编译器将试图查找java.lang.Employee(因为java.lang包被默认导入)、java.util.Employee、com.horstmann.corejava.Employee和当前包中的Employee。对这个类路径的所有位置中所列出的每一个类进行逐行查看。如果找到了一个以上的类,就会产生编译错误(因为类必须是惟一的,而import语句的次序却无关紧要)。
编译器的任务不止这些,它还要查看源文件(Source files)是否比类文件新。如果是这样的话,那么源文件就会被自动地重新编译。在前面已经知道,仅可以导入其他包中的公有类。一个源文件只能包含一个公有类,并且文件名必须与公有类匹配。因此,编译器很容易定位公有类所在的源文件。当然,也可以从当前包中导入非公有类。这些类有可能定义在与类名不同的源文件中。如果从当前包中导入一个类,编译器就要搜索当前包中的所有源文件,以便确定哪个源文件定义了这个类。
设置类路径
最好采用-classpath(或-cp)选项指定类路径:
java -classpath /home/user/classdir:.:/home/user/archives/archive.jar MyProg.java
或者
java -classpath c:\classdir;.;c:\archives/archive.jar MyProg
所有的指令应该书写在一行中。将这样一个长的命令行放在一个shell脚本或一个批处理文件中是一个不错的主意。
利用-classpath选项设置类路径是首选的方法,也可以通过设置CLASSPATH环境变量完成这个操作。其详细情况依赖于所使用的shell。命令格式如下:
export CLASSPATH=/home/user/classdir:.:/home/user/archives/archive.jar
在C shell中,命令格式如下:
setenv CLASSPATH /home/user/classdir:.:/home/user/archives/archive.jar
在Windows shell,命令格式如下:
set CLASSPATH=c:\classdir;,;c:\archives/archive.jar
直到退出shell为止,类路径设置均有效。
警告:有人建议将CLASSPATH环境变量设置为永久不变的值。总的来说这是一个很糟糕的注意。人们有可能会忘记全局设置,因此,当使用的类没有正确地加载进来时,会感到很奇怪。一个应该受到谴责的示例是Windows中Apple的QuickTime安装程序。它进行了全局设置,CLASSPATH指向一个所需要的JAR文件,但并没有在类路径上包含当前路径。因此,当程序编译后却不能运行时,众多的Java程序员花费了很多精力去解决这个问题。
警告:有人建议绕开类路径,将所有的文件放在jre/lib/ext路径。这是一个极坏的主意。其原因有两个:当手工地加载其他的类文件时,如果将它们存放在扩展路径上,则不能正常地工作(有关类加载器的详细信息,请参看卷II第9章)。此外,程序员经常会忘记3个月前所存放文件的位置。当类加载器忽略了曾经仔细设计的类路径时,程序员会毫无头绪地在头文件中查找。事实上,加载的是扩展路径上已长时间遗忘的类。