javac 中classpath的使用方法详解

来源:互联网 发布:windows 安全中心服务 编辑:程序博客网 时间:2024/05/21 06:33

Mastering the Java CLASSPATH

发表: keel 11:02:12 | 文库 | JAVA

1、class搜索路径的重要性

理解class搜索路径对所有Java开发人员来说都很重要,但是,IDE的广泛使用掩盖了这项技术,使大家普遍对它缺乏了解,甚至包括好多老鸟。这个问题在开发用于发布的应用程序(原文为distributed applications,但好像译为“分布式应用”有点晦涩)时尤其严重,因为应用程序运行时的系统环境可能和开发时的大不相同。

本文详细描述了某些Java类被其他代码引用时,Java编译器和JVM如何使用类搜索路径(class search path )定位这些类。这儿用一个非常简单的例子——同一个包中的两个类——来具体说明。我们将通过不同的方式来编译这两个类,根据classpath的设置不 同,编译可能成功也可能失败。

为了最清楚的说明这个问题,我们将只使用命令行工具进行编译。交互式开发工具有它们自己操作classpath的方法,这些方法因产品而异。

至于是由Java编译器在编译时定位需要的类,还是由JVM在运行时来做,这两种方法没有本质的区别。但编译器可以从源代码中编译需要的类,而JVM不行。下面的例子中我们用编译器来做,但在运行时的实现也完全类似。


2、例子

本例有两个很小的类:com.web_tomorrow.CPTest1 和 com.web_tomorrow.CPTest2,如下所示:
package com.web_tomorrow;
public class CPTest1
{
   public static void main(String[] args)
   {
    System.out.println ("Run CPTest1.main()");
   }
}

package com.web_tomorrow;
public class CPTest2
{
  public static void main(String[] args)
  {
    System.out.println ("Run CPTest2.main()");
    CPTest1 cpt1 = new CPTest1();
  }
}

Java代码组织的一个最基本规则就是`package name = directory name'(“包名 = 目录名”)。我们将为这两个类建立对应的目录结构,它们在包com.web_tomorrow中,所以我们创建目录
com/web_tomorrow来存放源代码: 
[root]
  com
    web_tomorrow
      CPTest1.java
      CPTest2.java

在本文中我用符号`[root]'来表示存放上述结构的任意目录,也就是说,根目录的位置由你决定。当然这会随安装这些文件的方式不同而不一样。

3基本原理

让我们来尝试用命令行工具javac来编译CPTest1.java。为了完全禁止类搜索路径(以防已有的设置影响本例),我们在javac上加上选项`-classpath ""'。

作为第一次试验,我们先换到CPTest1.java所在的目录下,并且尝试用javac和文件名进行编译。
cd [root]/com/web_tomorrow
javac -classpath "" CPTest1.java

操作成功,因为编译器能发现CPTest1.java (它就在当前的工作目录下),并且CPTest1没有引用任何其他类。输出文件CPTest1.class在CPTest1.java的相同目录下,因为 你没有给编译器任何信息让它作其它处理。到现在为止,一直都很好。现在让我们用同样的方式来试一下CPTest2。仍然在web_tomorrow目录 下,执行命令:
javac -classpath "" CPTest2.java

虽然目录还是刚才的目录,CPTest1和CPTest2也在相同的包里,这一次却失败了。错误信息可能是这样的:
CPTest2.java:7: cannot resolve symbol
symbol  : class CPTest1  
location: class com.web_tomorrow.CPTest2
  CPTest1 cpt1 = new CPTest1();
  ^

这一次和上一次成功的情况之间的区别之一就是CPTest2中有对CPTest1的引用:
  CPTest1 cpt1 = new CPTest1();

这次发生了什么呢?当编译器遇到对CP1Test的引用时,它会认为CP1Test类与当前编译的CP2Test类在同一个包里。这个假定是正确的,于是编译器来寻

找com.web_tomorrow.CP1Test,但它没有地方可以找,因为我们已经把类搜索路径明确的指定成了“”(也就是空)。

你可能认为,只要告诉编译器在当前目录下寻找就可以解决这个问题。在Unix和Windows系统中,“当前目录”的标准符号都是一个点号(.),也就是这样的命

令:
javac -classpath "." CPTest2.java

Fail Againt!跟刚才的例子完全一样,现在的问题是虽然CPTest1.java在当前目录下,但它实现的类不是CPTest1,而是 com.web_tomorrow.CPTest1,编译器将在当前目录下寻找目录com/web_tomorrow。也就是说,它在目录[root] /com/web_tomorrow/com/web_tomorrow中寻找一个Java源文件或类文件,它们事实上根本不存在,于是出错。

为了让编译器工作正常,我们不能让类搜索路径指向包含CPTest1的目录,而是按照Java标准中`package name = directory name'的规则,给类搜索路径指定编译器能找到CPTest1的根目录。这样才能工作,虽然不太好看:
javac -classpath "../.." CPTest2.java

在考虑如何变得不难看之前,看一下这个例子(仍在同一目录下)
javac -classpath "" CPTest1.java CPTest2.java

尽管classpath为空,这次却能工作。这是因为Java编译器会在命令行中明确列出的所有源代码中查找引用。如果要编译同一目录下的多个类,有一种很简单的方式:
javac -classpath "" *.java

'*.java'扩展为当前目录下所有.java文件的列表。这就说明为什么一次编译多个文件会成功,而同样的文件一个一个的编译却失败。

编译CPTest2有一个更方便的方法:
cd [root]
javac -classpath "." com/web_tomorrow/CPTest2.java

这次我们为CPtest2.java指定了完整的路径,而在-classpath选项中使用了'.'。另外,我们没有告诉编译器在当前目录下寻找文 件,而是让它从当前目录开始搜索。因为我们要找的类是com.web_tomorrow.CPTest1,编译器将在./com /web_tomorrow(也就是说当前目录下的com/web_tomorrow子目录)下寻找。这才是CPTest1.java正确的位置。

事实上,尽管我在命令行只指定了CPTest2,但事实上CPTest1同时也会被编译。编译器在正确的位置找到这个.java文件,但它不能判定这个.java文件是否包含正确的类,所以它编译这个文件。但请注意如果是:
cd [root]
javac -classpath "." com/web_tomorrow/CPTest1.java

就不会导致CPTest2.java被编译,因为编译器编译CPTest1时不需要知道CPTest2的任何事情。

类文件与.java文件分开的情况
到目前为止所有成功的例子都把输出的.class文件放到.java文件同样的位置,这是一种比较简单的模式,应用非常广泛。但很多开发者喜欢把源文件和生成的文件分开,因此它必须告诉编译器为.class文件维护不同的目录。下面我们来看看这对类搜索路径有何影响。

首先我们删除刚才几个例子产生的所有.class文件。我们还要有一个新的目录来存放生成的.class文件。命令行方式下的操作过程如下:
cd [root]
rm com/web_tomorrow/*.class
mkdir classes

如果你用Windows系统,别忘了把'/'替换成'\',现在的目录结构如下:
[root]
  com
    web_tomorrow
      CPTest1.java
      CPTest2.java
  classes

现在编译CPTest1.java,指定类文件的目标目录(通过-d选项)
cd [root]
javac -d classes -classpath "" com/web_tomorrow/CPTest1.java

成功,但你会注意到,.class文件根本不是直接放在classes目录中,而是有了一个新的目录结构:
[root]
  com
    web_tomorrow
      CPTest1.java
      CPTest2.java
  classes
    com
      web_tomorrow
        CPTest1.class

编译器建立了一个符合包结构的目录结构,这很有用,我们马上就会看到。当开始编译CPTest2.java时我们有两个选择,第一种是像上面一样,让编译器同时也

编译一次CPTest1;另一种是用-classpath选项指定刚才编译好的.class文件,这种方法更好一点,因为我们不必再来编译一遍CPTest1:
cd [root]
javac -d classes -classpath classes com/web_tomorrow/CPTest2.java

完成之后,我们的目录结构如下:
[root]
  com
    web_tomorrow
      CPTest1.java
      CPTest2.java
  classes
    com
      web_tomorrow
        CPTest1.class
        CPTest2.class

当然我们也可以在同一条命令中编译两个.java文件,结果完全相同。


4、classpath中的JAR打包文件

Java编译器和运行环境不仅可以在独立文件中搜索类,还可以在JAR打包文件中进行查找。一个JAR打包文件可以维护它自己的目录结构,Java 按照跟普通目录结构完全相同的方式,`directory name = package name',进行搜索。由于JAR本身就是一个目录,所以在类搜索路径中包含JAR打包文件时,路径必须引用JAR本身,而不是它所在的目录。如果我在目 录/myclasses下有一个JAR myclasses.jar,我需要指定:
javac -classpath /myclasses/myclasses.jar ...
而不仅仅是目录myclasses。

5、多个类搜索目录

在上面的例子中,每次我们都只让javac在一个目录下搜索。实际工作中,你的类搜索路径会包含很多目录和JAR文件。Javac的-classpath选项允许指定多个位置,但请注意,它的具体语法在Unix系统和Windows系统中稍有区别。
在Unix系统中是:
javac -classpath dir1:dir2:dir3 ...
而Windows系统则是:
javac -classpath dir1;dir2;dir3 ...

这是因为Windows使用冒号(:)作为文件名的一部分,因此不能作为文件名分隔符。当然目录分隔符也不一样:Unix的正斜杠(/)和Windows的反斜杠(\)。

6、系统classpath

除了在javac命令指定类搜索路径外,我们还可以使用'系统'类路径。如果命令中没有指定特定的路径,Java编译器和JVM都使用系统路径。在Unix和Windows系统中,系统路径都通过环境变量设置。例如在使用bash shell的Linux系统中:
CLASSPATH=/myclasses/myclasses.jar;export CLASSPATH 
在Windows中:
set CLASSPATH=c:\myclasses\myclasses.jar

这是暂时改变系统变量CLASSPATH的好方法,但如果你想这些变化一直保留下来,你就需要针对你的系统做一些修改。例如在Linux系统中,我会将这个命令放到我目录下的文件.bashrc中。而在Windows 2000/NT中可以通过‘控制面板’来修改。

如果你有很多随时用到的JAR,设置系统变量CLASSPATH就显得尤为重要。比如说,如果我在用Sun的J2EE参考实现开发EJB应用,所有 EJB相关的类都发布在一个叫做“j2ee.jar”的JAR打包文件中,我希望这个JAR一直都在类搜索路径中。另外,还有很多人希望无论当前目录是什 么,搜索路径始终包含当前目录。所以在我的.bashrc文件中就有这样一行:
CLASSPATH=/usr/j2ee/j2ee.jar:.;export CLASSPATH 
其中`.'是指'当前目录'

很容易看到命令行中的-classpath选项覆盖默认的系统类路径;它不是扩展而是覆盖。因此如果我们既要包含默认的系统路径,又要增加一些时怎 么处理呢?我们可以简单的通过-classpath选项同时列出默认值和我们要额外增加的部分。而一个更好的办法是引用系统变量CLASSPATH。当 然,这个语法对Windows系统和Unix系统是不同的。在Unix上:
javac -classpath $CLASSPATH:dir1:dir2 ...
其中$CLASSPATH扩展为系统变量CLASSPATH。在Windows上:
javac -classpath %CLASSPATH%;dir1:dir2 ...

0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 剖腹产液化伤口长的慢怎么办 内痔斑痕怎么办了能消化 油条面和稀了怎么办 解脲支原体感染怀孕怎么办 怀孕了检查出解脲支原体感染怎么办 大便是黑色的要怎么办 大人直肠给药不好意思怎么办 肛周脓肿长肉芽怎么办 二岁宝宝有直肠息肉怎么办 87岁老人得直肠息肉怎么办 做b超查出囊肿怎么办 解大便时肛门痒怎么办 生完孩子肛门痒怎么办 把达克宁软膏摸到肛门怎么办 痔疮手术后肛门痒怎么办 便秘拉完屁股疼怎么办 屁股拉屎拉破了怎么办 6岁小朋友屁眼痒怎么办 孩子屁屁偶尔痒怎么办 1岁半宝宝屁眼痒怎么办 3岁宝宝肛门痒怎么办 得痔疮发烧了怎么办啊 痔疮手术后肚子胀气怎么办 乙肝引起的发烧头痛怎么办 肠癌手术后吃了会胃疼怎么办 来月经肛门坠痛怎么办 总想排便还有血怎么办 闻了别人的口臭怎么办 有内痔肛门经常不舒服怎么办 做完肠镜肛门疼怎么办 3周宝宝得皮彦怎么办 痔疮有蚕豆大了怎么办 顺生肛门坠胀怎么办 生孩子痔疮脱出不能回纳怎么办 痔疮犯了屁眼疼怎么办 孕晚期得痔疮了怎么办 孕39周痔疮严重怎么办 顺产后长痔疮了怎么办 怀孕了犯痔疮了怎么办 怀孕了痔疮犯了怎么办 痔疮手术后肛裂怎么办