java很诱人,但对于刚跨入java门槛的初学者来说,编译并运行一个无比简单的java程序简直就是一个恶梦。明明程序没错,但各种各样让人摸不着头脑的错误信息真的让你百思不得其解,许多在java门口徘徊了很久的初学者就这样放弃了学习java的机会,很是可惜。笔者也经历过这个无比痛苦的阶段,感觉到编译难的问题就出在classpath的设置及对package的理解之上。本文以实例的方式,逐一解决在编译过程中所出现的各种classpath的设置问题。本文实例运行的环境是在windows xp + jdk 1.5.0。对其他的环境,读者应该很容易进行相应的转换。
1. 下载并安装jdk1.5.0,并按默认路径,安装到c:/program files/java/jdk1.5.0中。
2. 用鼠标单击windowsxp的“开始”->“运行”,在弹出的运行窗口中输入cmd,按确定或回车,打开一个命令行窗口。
3. 在命令行中输入:
java
有一列长长的洋文滚了出来,这是jdk告诉我们java这个命令的使用方法。其中隐含了一个重要信息,即jdk安装成功,可以在命令行中使用java此命令了。
4. 在命令行中输入
javac
屏幕显示:
'javac' 不是内部或外部命令,也不是可运行的程序或批处理文件。
这是由于windows找不到javac这个命令的原因。这就不明白了,java与javac都是jdk在同一个子目录里面的两个文件,为什么可以直接运行java而不能直接运行javac呢?原来,sun公司为了方便大家在安装完jdk后马上就可以运行java类文件,在后台悄悄地将java命令加入了path的搜索路径中,因此我们可以直接运行java命令(但我们是看不到它到底是在哪设置的,无论是在用户的path或系统的path设置中均找不到这个java存放的路径)。但sun所做的到此为止,其他jdk的命令,一概不管,需要由用户自己添加到搜索路径中。
5. 既然如此,那我们自己添加path的搜索路径吧。对“我的电脑”按右键,选“属性”,在“系统属性”窗口中选“高级”标签,再按“环境变量”按钮,弹出一个“环境变量”的窗口,在用户变量中新建一个变量,变量名为“path”,变量值为"c:/program files/java/jdk1.5.0/bin;%path%"。最后的%path%的意思是说,保留原有的path设置,且将目前的path设置新加到其前面。一路按“确定”退出(共有3次)。关掉原来的命令行窗口,依照第2步,重新打开一个新的命令行窗口。在此窗口中输入
javac
长长的洋文又出现了,这回是介绍javac的用法。设置成功。
6. so far so good. 到目前为止,我们已经可以编程了。但是,这不是一个好办法。因为随着以后我们深入学习java,我们就会用到junit、ant或netbeans等应用工具,这些工具在安装时,都需要一个名为指向jdk路径的“java_home”的环境变量,否则就安装不了。因此,我们需要改进第5步,为以后作好准备。依照第5步,弹出“环境变量”的窗口,在用户变量中新建一个变量,变量名为“java_home”,变量值为"c:/program files/java/jdk1.5.0"。注意,这里的变量值只到jdk1.5.0,不能延伸到bin中。确定后,返回“环境变量”的窗口,双击我们原先设定的path变量,将其值修改为“%java_home%/bin;%path%”。这种效果与第5步是完全一样的,只不过多了一个java_home的变量。这样,以后当我们需要指向jdk的路径时,只需要加入“%java_home%”就行了。至此,path路径全部设置完毕。一路确定退出,打开新的命令行窗口,输入
javac
如果长长的洋文出现,path已经设置正确,一切正常。如果不是,请仔细检查本步骤是否完全设置正确。
7. 开始编程。在c盘的根目录中新建一个子目录,名为“javatest”,以作为存放java源代码的地方。打开xp中的记事本,先将其保存到javatest文件夹中,在“文件名”文本框中输入"hello.java"。注意,在文件名的前后各加上一个双引号,否则,记事本就会将其存为"hello.java.txt"的文本文件。然后输入以下代码:
找不到hello.java了。我们要给它指定一个路径,告诉它到c:/javatest去找hello.java文件。输入
javac c:/javatest/hello.java
ok,这回不报错了,编译成功。
11. 输入
java c:/javatest/hello
这回屏幕出现:
exception in thread "main" java.lang.noclassdeffounderror: c:/javatest/hello
意思为在“c:/javatest/hello”找不到类的定义。明明c:/javatest/hello是一个.class文件,为什么就找不到呢?原来,java对待.java文件与.class文件是有区别的。对.java文件可以直接指定路径给它,而java命令所需的.class文件不能出现扩展名,也不能指定额外的路径给它。
那么,如何指定路径呢?对于java所需的.class文件,必须通过classpath来指定。
12. 依照第5步,弹出“环境变量”窗口,在用户变量中新建一个变量,变量名为“classpath”,变量值为"c:/javatest"。一路按“确定”退出。关闭原命令行窗口,打开新的命令行窗口,输入
java hello
“hello world”出来了。由此可见,在“环境变量”窗口中设置classpath的目的就是告诉jdk,到哪里去寻找.class文件。这种方法一旦设置好,以后每次运行java或javac时,在需要调用.class文件时,jdk都会自动地来到这里寻找。因此,这是一个全局性的设置。
13. 除了这种在环境变量”窗口中设置classpath的方法之外,还有另一种方法,即在java命令后面加上一个选项classpath,紧跟着不带扩展名的class文件名。例如,
java -classpath c:/javatest hello
jdk遇到这种情况时,先根据命令行中的classpath选项中指定的路径去寻找.class文件,找不到时再到全局的classpath环境变量中去寻找。这种情况下,即使是没有设置全局的classpath环境变量,由于已经在命令行中正确地指定类路径,也可以运行。
为了在下面的例子中更好地演示classpath的问题,我们先将全局的classpath环境变量删除,而在必要时代之以命令行选项-classpath。弹出“环境变量”窗口,选中“classpath”的变量名,按“删除”键。
此外,java命令中还可以用cp,即classpath的缩写来代替classpath,如java -cp c:/javatest hello。特别注意的是,jdk 1.5.0之前,javac命令不能用cp来代替classpath,而只能用classpath。而在jdk 1.5.0中,java及javac都可以使用cp及classpath。因此,为保持一致,建议一概使用classpath作为选项名称。
14. 我们再次人为地复杂化问题。关闭正在编辑hello.java的记事本,然后将javatest文件夹名称改为带空格的“java test”。在命令行中输入
javac c:/java test/hello.java
长长的洋文又出来了,但这回却是报错了:
javac: invalid flag: c:/java
jdk将带有空格的c:/java test分隔为两部分"c:/java"及"test/hello.java",并将c:/java视作为一个无效的选项了。这种情况下,我们需要将整个路径都加上双引号,即
javac "c:/java test/hello.java"
这回jdk知道,引号里面的是一个完整的路径,因此就不会报错了。同样,对java命令也需要如此,即
java -classpath "c:/java test" hello
对于长文件名及中文的文件夹,xp下面可以不加双引号。但一般来说,加双引号不容易出错,也容易理解,因此,建议在classpath选项中使用双引号。
15. 我们再来看.java文件使用了其他类的情况。在c:/java test中新建一个person.java文件,内容如下:
根据第11步,我们需要告诉jdk,到哪里去找所用到的类,即使这个被使用的类就与hello.java一起,同在c:/java test下面!输入
javac -classpath "c:/java test" "c:/java test/hello.java"
编译通过,jdk在c:/java test文件夹下同时生成了hello.class及person.class两个文件。实际上,由于hello.java使用了person.java类,jdk先编译生成了person.class,然后再编译生成hello.class。因此,不管hello.java这个主类使用了多少个其他类,只要编译这个类,jdk就会自动编译其他类,很方便。输入
java -classpath "c:/java test" hello
屏幕出现了
mike
成功。
16. 第15步说明了在hello.java中如何使用一个我们自己创建的person.java,而且这个类与hello.java是同在一个文件夹下。在这一步中,我们将考查person.java如果放在不同文件夹下面的情况。
先将c:/java test文件夹下的person.class文件删除,然后在c:/java test文件夹下新建一个名为df的文件夹,并将c:/java test文件夹下的person.java移动到其下面。在命令行输入
javac -classpath "c:/java test/df" "c:/java test/hello.java"
编译通过。这时javac命令没有什么不同,只需将classpath改成c:/java test/df就行了。
在命令行输入
java -classpath "c:/java test" hello
这时由于java需要找在不同文件夹下的两个.class文件,而命令行中只告诉jdk一个路径,即c:/java test,在此文件夹下,只能找到hello.class,找不到person.class文件,因此,错误是可以预料得到的:
exception in thread "main" java.lang.noclassdeffounderror: person
at hello.main(hello.java:3)
果真找不到person.class。在设置两个以上的classpath时,先将每个路径以双引号引起来,再将这些路径以“;”号隔开,并且每个路径与“;”之间不能带有空格。因此,我们在命令行重新输入:
java -classpath "c:/java test";"c:/java test/df" hello
编译成功。但也暴露出一个问题,如果我们需要用到许多分处于不同文件夹下的类,那这个classpath的设置岂不是很长!有没有办法,对于一个文件夹下的所有.class文件,只指定这个文件夹的classpath,然后让jdk自动搜索此文件夹下面所有相应的路径?有,只要使用package。
17. package简介。java中引入package的概念,主要是为了解决命名冲突的问题。比如说,在我们的例子中,我们设计了一个很简单的person类,如果某人开发了一个类库,其中恰巧也有一个person类,当我们使用这个类库时,两个person类出现了命名冲突,jdk不知道我们到底要使用哪个person类。更有甚者,当我们也开发了一个很庞大的类库,无可避免地,我们的类库中与其他人开发的类库中命名冲突的情况就会越来越多。总不能为了避免自己的类名与其他人开发的类名相同,而让每个编程人员都绞尽脑汁地将一个本应叫writer的类强行改名为sarkuyawriter,mikewriter, smithwriter吧?
现实生活中也是如此。假如你名叫张三,又假如与你同一单位的人中有好几个都叫张三,那你的问题就来了。某天单位领导在会上宣布,张三被任命为办公室主任,你简直不知道是该哭还是该笑。但如果你的单位中只有你叫张三,你才不会在乎全国叫张三的人有多少个,因为其他张三都分布在全国各地、其他城市,你看不见他们,摸不着他们,自然不会担心。
sun从这个“张三问题”受到了很大的启发,为解决命名冲突问题,就采取了“眼不见心不烦”的策略:将每个类都归属到一个特定的区域中,在同一个区域中的所有类,都不允许同名;而不同区域的类,由于相互看不到,则允许有同名的类存在。这样,就解决了命名冲突的问题,正如北京的张三与上海的张三毕竟不是同一人。这个区域在java中就叫package。由于package在java中非常重要,如果你没有定义自己的package,jdk将会你的类都归到一个默认的无名package中。
自定义package的名称可以由各个程序员自由创建。作为避免命名冲突的手段,package的名称最好足以与其他程序员的区别开来。在互联网上,每个域名都是唯一的,因此,sun推荐将你自己的域名倒写后作为package的名称。如果你没有自己的域名,很可能只是因为囊中羞涩而不去申请罢了,并不见得你假想的域名与其他域名发生冲突。例如,笔者假想的域名是sarkuya.com,目前就是唯一的,因此我的package就可以定名为com.sarkuya。谢谢java给了我们一个免费使用我们自己域名的机会,唯一的前提是倒着写。当然,每个package下面还可以带有不同的子package,如com.sarkuya.util,com.sarkuya.swing,等等。
定义package的方式是在相应的.java文件的第一行加上“package packagename;”的字样,而且每个.java文件只能有一个package。实际上,java中的package的实现是与计算机文件系统相结合的,即你有什么样的package,在硬盘上就有什么样的存放路径。例如,某个类的package名为com.sarkuya.util,那么,这个类就应该必须存放在com/sarkuya/util的路径下面。至于这个com/sarkuya/util又是哪个文件夹的子路径,第18步会谈到。
package除了有避免命名冲突的问题外,还引申出一个保护当前package下所有类文件的功能,主要通过为类定义几种可视度不同的修饰符来实现:public, protected, private, 另外加上一个并不真实存在的friendly类型。
对于冠以public的类、类属变量及方法,包内及包外的任何类均可以访问;
protected的类、类属变量及方法,包内的任何类,及包外的那些继承了此类的子类才能访问;
private的类、类属变量及方法,包内包外的任何类均不能访问;
如果一个类、类属变量及方法不以这三种修饰符来修饰,它就是friendly类型的,那么包内的任何类都可以访问它,而包外的任何类都不能访问它(包括包外继承了此类的子类),因此,这种类、类属变量及方法对包内的其他类是友好的,开放的,而对包外的其他类是关闭的。
前面说过,package主要是为了解决命名冲突的问题,因此,处在不同的包里面的类根本不用担心与其他包的类名发生冲突,因为jdk在默认情况下只使用本包下面的类,对于其他包,jdk一概视而不见:“眼不见心不烦”。如果要引用其他包的类,就必须通过import来引入其他包中相应的类。只有在这时,jdk才会进行进一步的审查,即根据其他包中的这些类、类属变量及方法的可视度来审查是否符合使用要求。如果此审查通不过,编译就此卡住,直至你放弃使用这些类、类属变量及方法,或者将被引入的类、类属变量及方法的修饰符改为符合要求为止。如果此审查通过,jdk最后进行命名是否冲突的审查。如果发现命名冲突,你可以通过在代码中引用全名的方式来显式地引用相应的类,如使用
java.util.date = new java.util.date()
或是
java.sql.date = new java.sql.date()。
package的第三大作用是简化classpath的设置。还记得第16步中的障碍吗?这里重新引用其java命令:
java -classpath "c:/java test";"c:/java test/df" hello
我们必须将所有的.class文件的路径一一告诉jdk,而不管df其实就是c:/java test的子目录。如果要用到100个不同路径的.class文件,我们就得将classpath设置为一个特别长的字符串,很累。package的引入,很好地解决了这个问题。package的与classpath相结合,通过import指令为中介,将原来必须由classpath完成的类路径搜索功能,很巧妙地转移到import的身上,从而使classpath的设置简洁明了。我们先看下面的例子。
18. 先在hello.java中导入df.person。代码修改如下:
二是在每次的javac及java命令行中自行设置classpath,这也是本文使用最多的一种方式,其优点是不加重系统环境变量的负担;
三是根据import指令,将其内容在后台转换为classpath。jdk将读取全局的环境变量classpath及命令行中的classpath选项信息,然后将每条classpath与经过转换为路径形式的import的内容相合并,从而形成最终的classpath. 在我们的例子中,jdk读取全局的环境变量classpath及命令行中的classpath选项信息,得到c:/java test。接着,将import df.person中的内容,即df.person转换为df/person, 然后将c:/java test与其合并,成为c:/java test/df/person,这就是我们所需要的person.class的路径。在hello.java中有多少条import语句,就自动进行多少次这样的转换。而我们在命令行中只需告诉jdk最顶层的classpath就行了,剩下的则由各个类中的import指令代为操劳了。这种移花接木的作法为我们在命令行中手工地设置classpath提供了极大的便利。
应注意的一点是,import指令是与package配套使用的,只有在某类通过“package pacakgename;”设定了包名后,才能给其他类通过import指令导入。如果import试图导入一个尚未设置包的类,jvm就会报错。
19. 我们接下来看,当使用jdk类库时,classpath如何设置。
20. 修改hello.java,内容如下:
| import df.person; import java.util.date; public class hello { public static void main(string[] args) { date date = new date(); system.out.println(date); person person = new person("mike"); system.out.println(person.getname()); } } |
21. jdk类库存放于c:/program files/java/jdk1.5.0/jre/lib/rt.jar文件中。关于jar文件的介绍,已经超出了本文的范围,感兴趣的读者可以阅读horstmann写的core java一书。
jar文件可以用winrar打开。用winrar打开后,可以看到里面有一些文件夹,双击其中的java文件夹,再双击util的文件夹,可以在看到date.class文件就在其中。如果你看过data.java或其他jdk类库的源码(在c:/program files/java/jdk1.5.0/src.zip文件中),你就会发现,像java、util这些文件夹均是package。这也是hello.java第2行中使用了import指令的原因。
我们可以通过winrar的查找功能来定位某个类所在的包。在“查找文件”的窗口中的“要查找的文件名”文本框中输入date.class,就会查找出在rt.jar文件中存在两个date.class文件,一个是java/sql/date.class,另一个是java/util/date.class。其中,sql下面的date.class文件与数据库有关,并非我们这里所需,java/util/date.class才是我们所要的。
rt.jar文件就像本文中的c:/java test中一样,是jdk类库的唯一入口。我们可以在命令行的classpath选项指定.jar文件。需要注意,.jar文件的classpath设置有些特珠。在以前的例子中,我们设置classpath时都是设置了路径就行了,而对于.jar文件,我们必须将.jar文件名直接加到classpath中。
22. 在命令行输入
javac -classpath "c:/program files/java/jdk1.5.0/jre/lib/rt.jar";"c:/java test" "c:/java test/hello.java"
java -classpath "c:/program files/java/jdk1.5.0/jre/lib/rt.jar";"c:/java test" hello
这样当然没有问题,因为我们指定了rt.jar文件及c:/java test两个classpath。但且慢,在命令行输入:
javac -classpath "c:/java test" "c:/java test/hello.java"
java -classpath "c:/java test" hello
不可思议的是,编译及运行成功了!令人惊讶的是在我们将classspath只设置为c:/java test的情况下,jdk如何得出java.util.date的classpath?
原因在于,就像java的path路径已经悄悄在后台设置好一样,rt.jar的classpath路径也悄悄地在后台设置了。因此,我们不必多此一举手工设置其classpath了。
23. 最后一点需要谈到的是,如果主类恰好也在一个package中(在大型的开发中,其实这才是一种最常见的现象),那么java命令行的类名前面就必须加上包名。
在c:/java test下面新建一个文件夹,名为nf。将c:/java test下面的hello.class删除,将hello.java移到nf文件夹下。打开nf文件夹下的hello.java,为其设置package属性。
| package nf; import df.person; import java.util.date; public class hello { public static void main(string[] args) { date date = new date(); system.out.println(date); person person = new person("mike"); system.out.println(person.getname()); } } |
编译与以前没啥区别,只不过是修正一下改过之后的路径。
javac -classpath "c:/java test" "c:/java test/nf/hello.java"
而java命令行却有了变化
java -classpath "c:/java test" nf.hello
上面命令行语句中,nf.hello告诉jdk,hello.class在nf的package下面。
至此,本文有关classpath及package的问题的讨论已经全部结束。由此可见,java的入门的确非常不易。如果初学java的程序员一见到java的编译竟是如此的复杂,多半就会抽身而退。因此,笔者认为,sun在j2se的tutorial中故意将编译的问题尽量简单化,以吸引更多的java初学者。一旦品尝了java的香醇可口的美味后,就不用担心他们退出了,因为咖啡是非常容易让人上瘾的。
闽公网安备 35060202000074号