Java之classpath

1.class搜索路径的重要性

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

本文详细描述了某些java类被其他代码引用时,java编译器和jvm如何使用类搜索路径定位这些类。这儿用一个非常简单的例子——同一个包中的两个类——来具体说明。我们将通过不同的方式来编译这两个类,根据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 ...


======================================================
在最后,我邀请大家参加新浪APP,就是新浪免费送大家的一个空间,支持PHP+MySql,免费二级域名,免费域名绑定 这个是我邀请的地址,您通过这个链接注册即为我的好友,并获赠云豆500个,价值5元哦!短网址是http://t.cn/SXOiLh我创建的小站每天访客已经达到2000+了,每天挂广告赚50+元哦,呵呵,饭钱不愁了,\(^o^)/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值