“包”机制是java中特有的,也是java中最基础的知识。一些初学java的朋友,通常象学其它语言一样从教材上copy一些程序来运行,可是却常常遇到莫名其妙的错误提示。这些问题事实上都出在对“包”的原理不够清楚。本文将就此问题进行深入阐述。
一、为什么java中要有“包”的概念?
以一言概之,java中“包”的引入的主要原因是java本身跨平台特性的需求。因为java中的所有的资源也是以文件方式组织,这其中主要包含大量的类文件需要组织管理。java中同样采用了目录树形结构。虽然各种常见操作系统平台对文件的管理都是以目录树的形式的组织,但是它们对目录的分隔表达方式不同,为了区别于各种平台,java中采用了“.”来分隔目录。
二、java中包结构和平台的衔接
java中的资源存在于不同平台下时必然会有很大差异。因此跨平台的java包结构和平台之间必须通过一种方式来衔接到一起。事实上它们就是通过我们很熟悉的classpath的设置来衔接到一起的。举个例子:
我在Windows2000环境下的classpath设置如下 :
classpath = d:/jdk1.4.2/lib/dt.jar; d:/test/com
类的衔接关系可以用下图来表达:
图1
从图中可以看出,java中的类的组织是“悬空”的,这样的话,它们可以随意放在任意平台下,但是要在该平台下正确找到一个类,则必须使用classpath来设置类所在目录的前面一部分(即区别于平台的部分)。在java中常常把一棵类树压缩成一个.jar文件,如图中的rt.jar,这并不影响对类的查找,在指定环境变量时可以指定.jar文件所在目录,也可以具体指明.jar的完全查找路径,即上例中的classpath中也可描述成:
classpath = d:/jdk1.4.2/lib; d:/test/com
当正确设置了平台下的classpath环境变量时,java跨平台的特性就体现出来了。即你在编写程序中,描述一个类时,就不用具体指明它的完整路径,而是仅仅指明java中的类路径就可以了,即指出图1中竖线右边的查找路径就可以了。这样的话,你编写的程序拿到任何平台下时,只需要根据类文件的存放目录来书写相应的classpath环境变量,而不用因为类的存放环境变化而修改程序。
注意:java中对于某个类的查找是把classpath中的每一项逐一连接,当一个连接能够正确找到相关类后,便不再向后查找。
三、正确使用“包”
在使用包的过程中有很多需要注意的小细节,这里把常见的问题都列举如下:
1、对类路径的设置通常有两种方法:
i)在系统的环境变量中设置,设置方法依据平台而变;
ii)以命令参数的形式来设置。
如:javac –classpath d:/jdk1.4.2/lib d:/test/com/edu/test/TestFile.java
java –classpath .;d:/jdk1.4.2/lib; d:/test/com edu.test.TestFile
注意:i) javac和java命令有很大区别,可以这样区分,javac是一个平台命令,它对具体的平台文件进行操作,要指明被编译的文件路径。而java是一个虚拟机命令,它对类操作,即对类的描述要用点分的描述形式,并且不能加扩展名,还要注意类名的大小写。
ii)有一个很奇怪的问题,即javac命令后面的classpath默认包含当前目录(符合windows的习惯),可是在java命令后面的classpath却不包含当前目录,所以在它的classpath中一定不能忘了加上当前目录的描述,即加上“.”。
2、在java程序中对类路径的描述用“.”分隔,而且也有当前目录的概念。如要运行图1中的TestFile必须指明为 edu.test.TestFile。但是如果在类TestFile中要调用和它在同一目录中的TestString,则不必指明目录前缀。
3、在java程序中所有使用到的类都应该清楚的指明这个类的查找路径。一般有两种方法指明:i)在程序的开始使用import关键字指明。如类TestFile中要用到FileInputStream类,则在程序头中加入import java.io.FileReader; 或import java.io.*;
ii)在程序用用到FileFileReader类处直接写完整路径,如:
java.io.FileFileReader fin = new java.io.FileReader(“filename”);
注意:java.lang包总是被默认导入的。
4、类的目录结构一定要和类中第一句“包声明”一致。如类TestFile.class对应的.java文件的第一句必须包含:package edu.test;
确保类的存放路径和类中指明的“包路径”一致的方法一般有两种:
i)编写.java文件时存放的目录事先确定好,如TestFile.java就直接放在edu/test目录下,然后用下面的语句编译:
javac –classpath d:/jdk1.4.2/lib d:/test/edu/test/TestFile.java
当编译完成后,产生的TestFile.class文件会出现在编译命令中java文件的描述路径中。即出现在d:/test/edu/test中
ii)通过-d参数的使用来编译程序。如使用下面的语句来编译:
javac –d d:/test d:/test/edu/test/TestFile.java
将在-d后指定的目录d:/test下面自动按照packagek中指定的目录结构来创建目录,并且将产生的.class文件放在这个新建的目录下,即在d:/test下面建立/edu/test目录,然后产生的TestFile.class放在d:/test/edu/test目录下。
5、为了便于工程发布,可以将自己的类树打成.jar文件。如将图1中的edu下面的所有类文件打成一个.jar文件,可以先转到d:/test目录,再用下面的命令:
jar –cvf test.jar edu/
这时会在d:/test下产生一个test.jar文件,此.jar文件中包含edu/下的完整目录结构和文件。使用这个.jar文件时,只需在classpath中指明.jar文件的存放路径即可。
四、举例
本例用于统计一个文本文件中的单词数,注释中的编号对应前一节的编号:
//TestFile.java package edu.test;//--------------------------------------- 4 import java.io.FileReader;//------------------------------ 3 import java.io.LineNumberReader; class TestFile{ public static void main(String []argv){ TestString ts = new TestString();//---------------- 2 FileReader fin; LineNumberReader line = null; int wordNum = 0; try{ fin= new FileReader("word.txt"); line = new LineNumberReader(fin); }catch(Exception e){ e.printStackTrace(); System.exit(0); } while(true){ try{ String temp = line.readLine(); wordNum += ts.CountWord(temp); }catch(Exception e){ break; } } try{ line.close(); }catch(Exception e){}; System.out.println("Word count is:" + wordNum); } } |
//TestString.java package edu.test; import java.util.*; class TestString { int CountWord(String str){ StringTokenizer token = new StringTokenizer(str); return token.countTokens(); } } |
两个.java文件存放在d:/temp目录下,当前目录为d:temp使用下面的命令进行编译:
d:/temp>javac –classpath d:/jdk1.4.2/lib –d d:/test *.java
用下面的命令运行:
d:/temp> java –classpath .;d:/jdk1.4.2/lib; d:/test/com edu.test.TestFile //-------------- 1
如果需要打包的话,先转到d:/test,然后用下面命令:
jar –cvf test.jar edu/ //--------------------------------------------------- 5
这时可产生一个test.jar文件,可将此文件置于任何平台下使用。
(如需要讨论文中涉及的问题请联系chenjm2000@hotmail.com)