java package 概念 zz
普及java package import机制很好的一篇文章,耐心看完收获会很大。
java应用程序文件
这个说法是我自己抽象出来的,指的是一般的组织在package中的所有文件。
大体分成这三种:
1,java程序源文件,扩展名为.java。
2,编译好的java类文件,扩展名为.class。
3,其他文件,除了以上的,就是资源文件。
例如图片文件,xml文件,mp3文件
等等等等都可以组织在package之中。(你当然也可以把一部电影放在package里面,当然
不提倡这样做)
package干吗用?
package就是组织文件的一种方式。
最大的作用就是组织java类文件。
成千上万的class文件,总难免重名吧,即便不重名,那么多你记得住么?当然要分门别类
的组织起来方便你随时取用吧,这个就是package最大的作用。
package是个什么东西?
package本身是一个比较抽象的逻辑概念,它的宗旨是将东西组织在一棵树里面,就好像
linux的文件系统一样,它有一个根,然后从根开始有目录和文件,然后目录中也可以有
目录。而实现的时候是怎样的呢?是利用文件系统的目录结构或者利用压缩文件中的目录
结构来组织的。
package怎么实现的?
首先讲其最重要的应用——组织源代码。
我们知道java源代码的基本单元是一个.java文本文件。
一个.java文件可以包含一个public类和无数包级类,
默认什么访问修饰都没有的类就是一个包级类。这种类只能在本package中使用。
当然了,类当中还可以有类,名唤内隐类,内隐类的访问修饰就可以是protected
或者甚至是private了。
好,那么何谓本package呢?
当你在源代码的最开头(当然,注释可以放在更开头的位置)不写package语句的话,那么
当前编译单元中的所有编译出来的类就只能够放在package的"/"上,这种package我们叫做
default package。一般我们写个HelloWorld或者简单调几个api实现个小功能的话,一般
采用这种“不打包”似的default package就可以了。
但是,当你的类开始泛滥起来的时候,你就必须考虑给类打个包放在某个package中了。
方法很简单,在所有代码开始之前写一句
package 包名;
即可。
包名是用"."间隔的字符串,一般能够当作文件名的字符串就能用到这里来。
比如
package aaa.bbb.ccc;
意味着这个编译单元所编译出来的class都必须放在/aaa/bbb/ccc这个目录之下。
否则这种类就无法正常使用,正是这种机制保证了类的严格组织。
实际上,当你在编译一些有package语句的编译单元的时候,如果你使用的是命令行的话,
应该使用-d参数让编译器自己生成目录结构,实际上你的.java就应该放在这种目录结构
之中。我举个非常简单的例子,有个人写了这么两个.java文件。
/* A.java */
package aaa.bbb.ccc;
public class A{
B b=new B();
}
/* B.java*/
package aaa.bbb.ccc;
public class B{}
然后这个人就把这两个文件就放在当前目录之下,然后javac A.java,
然后傻眼。
说找不到B,怎么找不到呢?
为什么找不到呢,不是在一个package里面吗?
对,是在一个package里面,但是B.java必须放在当前目录/aaa/bbb/ccc下面才可用,
当javac编译A.java的时候,它发现当前所在的包是aaa.bbb.ccc,然后需要一个B类,
没有B.class,就要去找B.java,而B.java的package语句和自己所在目录结构不符,没法
使用,所以就傻眼了。
这个人就知道了把B.java放到当前目录/aaa/bbb/ccc里面,嘿,编译通过了。
没错,生成了A.class,但是A.class还是不可用的,因为它不在它应该在的地方。
我们又必须将它放到它应该在的地方,它才能好好运作。
那你说我把A.java也放在当前目录/aaa/bbb/ccc里面,然后
javac aaa.bbb.ccc.A.java
漂亮吧?
不行,为什么?因为javac接受的参数是一个文件系统的文件名,javac命令行中的参数
只能是一个完整的路径名。
那我把A.java放在当前目录/aaa/bbb/ccc里面,然后
javac aaa/bbb/ccc/A.java
这下好了吧,恩,差不多,但是生成的class还是直接丢在了源代码目录之下
最好是
javac -d bin aaa/bbb/ccc/A.java
这样就会在当前目录的bin目录下看到完整的目录结构以及放置妥当的class文件。
package与classpath不得不说的事
讲了半天还是觉得不太清楚package的"/"到底在哪里,在当前目录吗?
如果不知道package的"/"在哪里,那那些package中的文件在哪里就更无从得知了。
回答很简单,package的"/"在classpath中。
头痛了,package没有搞懂的,classpath一般也不会很懂。
没事,classpath说白了就是一堆path,这些该死的path有的指向一个目录,有的指向一个
文件(只能是zip或者jar两种,当然更保险的话还是用jar比较好)。
对于java虚拟机而言,它并不想过问这些path的八卦,它所做的是一视同仁!
它要的是所有这些path中的内容,对,对它而言,它不在乎内容是来自一个目录还是来自
一个文件,总而言之,所有的这些东西在他眼里都一样。就好像它空出一块地方,把所有
的文件解压,把所有的目录中的内容保留目录结构的送入相应位置,形成一套它自己感
兴趣的package组织方式。它要的就是这种方式来访问。
所以我可以在a.jar中有一个目录结构aaa/bbb/ccc/A.class,然后在b.jar中也有一个同样
的目录结构aaa/bbb/ccc/B.class。当我把这两个文件都放在classpath中的时候,
对虚拟机而言,这个A.class和这个B.class就是放在同一个package中的。
前面的那个实践也透视了这一点,实际上编译A.java的时候,是在classpath中寻找class
文件的,找不到,则会在sourcepath中寻找其源代码文件(不设定的话,sourcepath就是等
于classpath),当我们把B.java放在当前目录中合适的目录结构中的时候,它就会找到
这个文件并将它编译,然后进行使用。因为当前目录"."是在classpath中的。
原则就是,对java而言,它是忽略文件系统细节的,它要求的只是自己的classpath
中按照约定存放的资源。
(以上classpath对应的classloader是app classloader)
import干吗用的?
java几乎所有的api都是用java语言本身写成的,是的,那些你所经常使用的String,
HashSet,HashMap等等这些类都是用java语言写成的。
这些类的制作是我们也可以来做的事情,他们同样是写出一个个java文件,打上不同的
package,然后编译好,最后压缩一下,成为jre里面的dt.jar之类的文件,变成了我们常
常调用的库。
有的人以为,当我们写下import java.util.*;之前,我们的这些库就是“不可达”的,
而当我们写下了import java.util.*之后,我们所需要的那个库文件就会被某种不知道的
机制加入到我们的工程。
错!这些人是纯粹打死不看书的想当然类型,只在乎自己的意象,不在乎其所以然。
不管你写还是不写import语句,对当前外部的编译和执行环境都不会有任何的影响。
即便你不写import java.util.*;
你照样可以用java.util.HashMap来调用系统api,根本不用做更多的事情。
这里我不得不再提一次,不要把dt.jar放到CLASSPATH环境变量中,它已经在classpath中
了,而且此举常常让初学者丢掉了默认在classpath中的"."当前目录,让他们一番热情学
java写helloworld的火焰被一盆水扑灭。
import有啥用的呢?
它只不过是一种让你少打点字的方法,就跟c++的名称空间的using语句功能一样,就是暴
露那个package下面所有的类,写代码的时候就可以直接写类名,而不用写下完整的
包名.类名,仅此而已。
不得不提的是,*仅仅代表类名,不负责以下更深层次中各级目录中的东西。
import java.*;实际上没有暴露任何类,因为没有类是打在这个包里面的。它没有
暴露java.util.*,也没有暴露java.sql.*等等,它仅仅暴露了java目录下面所有的类。
打包成jar文件
一般的打包成jar文件,除了将目录结构压缩存储在一个zip格式但是扩展名比较畸形的
文件中之外没有做任何事情。
jar文件可以用任何能够解压zip的工具来解压,如果你讨厌使用jar命令行,你甚至可以
直接使用winrar来压缩,然后改个名字即可。
当然了,jar文件也有别的用处,比如“可直接执行的jar”。
“可直接执行的jar”跟“不可直接执行的jar”的区别仅仅在于它多了一个配置文件在
jar文件中,这个文件中指出了一个完整的类名,告诉java.exe或者javaw.exe,main方法
这个类里面而已。
欲知道更多关于jar文件的用法,请参考精华区。
贴二——package从实践到理论:
package就是catalog,所谓目录册。
什么是目录册呢?比较抽象的说,就是让你快速定位一个东西的册子。
比如电话簿,比如字典,比如搜索引擎。
java中,主要是class文件,当然也可以是任何别的文件,就是通过package来组织的。
package有点类似一个虚拟的文件系统,可以将所有的东西组织在树形结构之中。
这有一个根"/",也就是我们什么package语句都不写的情况下class文件所应该处的位置。
直接赤裸裸放在classpath中的东西都是放在"/"下面,然后如果有package语句,则这个
class文件或者java源代码文件都要放在和package语句中包名对应的目录结构之中。
1,什么都别说,先跟着我来做一把
我们先找一个目录,比如C:myjob
然后我们建立两个目录,一个叫做src,一个叫做bin
C:myjob>md src
C:myjob>md bin
C:myjob>dir
驱动器 C 中的卷是 LIGHTNING
卷的序列号是 3DD1-83D9
C:myjob 的目录
2005-12-25 14:33 <DIR> .
2005-12-25 14:33 <DIR> ..
2005-12-25 14:34 <DIR> src
2005-12-25 14:34 <DIR> bin
0 个文件 0 字节
4 个目录 305,123,328 可用字节
C:myjob>
然后我们在src目录中去写程序
C:myjob>cd src
C:myjobsrc>
我们写这么4个java文件
////A.java
package com.lightning;
public class A{
{System.out.println("com.lightning.A");}
}
////B.java
package com.lightning;
public class B{
{System.out.println("com.lightning.B");}
}
////C.java
package com;
public class C{
{System.out.println("com.C");}
}
////Test.java
package net.test;
import com.lightning.*;
import com.*;
public class Test{
public static void main(String[] args)
{
new A();new B();new C();
System.out.println("net.test.Test");
}
}
写好之后就是这样
C:myjobsrc>dir
驱动器 C 中的卷是 LIGHTNING
卷的序列号是 3DD1-83D9
C:myjobsrc 的目录
2005-12-25 14:34 <DIR> .
2005-12-25 14:34 <DIR> ..
2005-12-25 14:39 86 A.java
2005-12-25 14:40 86 B.java
2005-12-25 14:42 194 Test.java
2005-12-25 14:43 68 C.java
4 个文件 434 字节
2 个目录 305,106,944 可用字节
然后我们建立com目录,comlightning目录,nettest目录
C:myjobsrc>md com
C:myjobsrc>md comlightning
C:myjobsrc>md nettest
我们将Test.java放入nettest中去
将A.java,B.java放入comlightning中去
将C.java放入com中去
C:myjobsrc>move Test.java nettest
C:myjobsrc>move A.java comlightning
C:myjobsrc>move B.java comlightning
C:myjobsrc>move C.java com
然后我们在c:myjobs中发令:
C:myjobsrc>cd ..
C:myjob>javac -sourcepath src -d bin srcnettestTest.java
然后我们看看bin目录中多了什么
C:myjob>dir bin /s
驱动器 C 中的卷是 LIGHTNING
卷的序列号是 3DD1-83D9
C:myjobbin 的目录
2005-12-25 14:34 <DIR> .
2005-12-25 14:34 <DIR> ..
2005-12-25 14:46 <DIR> net
2005-12-25 14:46 <DIR> com
0 个文件 0 字节
C:myjobbinnet 的目录
2005-12-25 14:46 <DIR> .
2005-12-25 14:46 <DIR> ..
2005-12-25 14:46 <DIR> test
0 个文件 0 字节
C:myjobbinnettest 的目录
2005-12-25 14:46 <DIR> .
2005-12-25 14:46 <DIR> ..
2005-12-25 14:46 520 Test.class
1 个文件 520 字节
C:myjobbincom 的目录
2005-12-25 14:46 <DIR> .
2005-12-25 14:46 <DIR> ..
2005-12-25 14:46 <DIR> lightning
2005-12-25 14:46 338 C.class
1 个文件 338 字节
C:myjobbincomlightning 的目录
2005-12-25 14:46 <DIR> .
2005-12-25 14:46 <DIR> ..
2005-12-25 14:46 354 A.class
2005-12-25 14:46 354 B.class
2 个文件 708 字节
所列文件总数:
4 个文件 1,566 字节
14 个目录 305,057,792 可用字节
然后我们执行,同样在c:myjobs下发令
C:myjob>java -cp bin net.test.Test
com.lightning.A
com.lightning.B
com.C
net.test.Test
2,从实践到理论
刚才我用一个非常简单但是非常完整的例子给大家演示了java的package机制。
为什么以前脑海里面那么简单的javac会搞得这么复杂呢?
实际上它本就这么复杂,
并不是一个.java,一行javac一个当前目录中的class这么简单。
首先我要打破一些东西,然后才好建立一些东西。
javac并非只是给一个.java编译一个class的。javac自带有make机制,也就是说,如果在
javac的参数中java文件使用到的任何类,javac首先会去找寻这个类的class文件存在与否
,如果不存在,就会在sourcepath中找寻源代码文件来编译。
什么是sourcepath呢?sourcepath是javac的一个参数,如果你不加指定,那么sourcepath
就是classpath。
比如我们装好jdk之后,我说过不要设定classpath环境变量,因为大部分人一旦设定了
classpath,不是多此一举把什么dt.jar放进去。(我可以好好打击你一下,告诉你一个可
悲的事实——jre永远不会从你这个classpath中去寻找dt.jar。你完全是徒劳的!)就是
把"."搞不见了,搞得是冷水一盆盆的往自己身上泼,脑袋一点点的涨大。
不要设!classpath没有你想象中那么普适和强大,它只是命令行的简化替代品。
你不设的话它就是"."。
回到sourcepath,sourcepath指的是你源代码树的存放地点。
为什么是源代码树?而不是一个目录的平板源代码呢?
请大家将原本脑海中C的编译过程完全砸掉!
java完全不同,java没有头文件,每个.java都是要放在源代码树中的。
那么这颗树是怎么组织的呢?
对了,就是package语句。
比如写了package com.lightning;
那么这个.java就必须放在源代码树根的comlighting之下才行。
很多浮躁的初学者被default打包方式宠坏了。自我为中心,以为java就是一套库,自己写
的时候最多import进来就行了,代码从不打包,直接javac,直接java,多么方便。
孰不知自己写的这个.java也不过是java大平台的其中一个小单元而已。如果不打包,
我写一个Point,你写一个Point,甚至更有甚者敢于给自己的类起名叫String等等。
全部都在平板式的目录中,那jre该选哪一个?
一旦要使用package语句,就要使用代码树结构,当然,你要直接javac,也行。
不过javac出来的这个class要放在符合package结构的目录中才能发挥正常作用,否则就是
废物一坨。
而且,如果你这个.java还用到其它自己写的有package语句的.java,那这个方法就回天乏
术了。
按照sun的想象,应该是写好的.java放在符合package结构的目录中,package语句保证了
正确放置,如果目录位置和package语句中指示的不同,则会出错。
所以按照刚才我们的那种package写法,我们必然要将那几个.java文件放入相应的目录中
才能让javac顺利找到他们来make。
有人说javac可不可以像java那样 java aaa.bbb.c...java?
不可以
javac中的那个.java文件参数必须是一个文件系统的路径文件名形式。
然后如果用到其它的.java,javac会根据目前的sourcepath出发寻找目录结构中的那些
java文件。
当然了,既然打了包,在使用的时候。
要么写全名——包名.类名
或者使用import
不得不提的是,import就好比c++的using,它不负责做文件操作,它只是方便你写代码。
另外import语句中的*代表的是类名,不代表包名片断。
你import com.*;
编译器仍然找不到com.lightning中的任何类。
反之亦然。
就好象你告诉编译器,我这里面要用到姓诸葛的人。
那么姓诸的人当然编译器不会认为也包含在内。
所以,如果程序一旦写到一定规模。
就不得不使用ant来管理这些。
或者使用IDE,否则jdk就真的变成了java developer killer。
但是对于初学者,在了解为什么会有ant之类的之前,还是要体会一下使用
jdk的艰辛。
这个和以前在unix上开发的人用gcc命令行到后来使用make之后使用ide
之类的时候的体会是类似的。
最后,javac的-d参数是指示编译出来的class文件放在哪里的,如果你不指定的话,它们
和.java混在一起。当然也符合目录结构。