快速上手一门新语言,我个人认为很重要的一点就是:理解它是如何引入包/模块的。
这对于我们组织项目结构,引入第三方依赖都是非常重要的,相信大家都能理解。
今天讲一下JAVA是如何进行包查找的,理解它对我们学习JAVA至关重要。
首先创建空的项目目录:
/Users/liangdong/eclipse-workspace/mine
创建一个src目录保存项目的所有源码:
liangdongs-MacBook-Pro:mine liangdong$ pwd /Users/liangdong/eclipse-workspace/mine liangdongs-MacBook-Pro:mine liangdong$ ll total 0 drwxr-xr-x 2 liangdong staff 64 8 6 13:20 src
因为JAVA开源社区有很多很多包,每一个包里很多有class。
为了避免和开源包冲突,JAVA惯用法一般是用域名的反序作为包名,以避免冲突。
所以我创建一个包叫做:cc.yuerblog,对应目录必须是这样的:
liangdongs-MacBook-Pro:mine liangdong$ ll total 0 drwxr-xr-x 2 liangdong staff 64 8 6 13:20 bin drwxr-xr-x 2 liangdong staff 64 8 6 13:20 src liangdongs-MacBook-Pro:mine liangdong$ mkdir -p src/cc/yuerblog
这种存放方式是JAVA的包查找机制决定的,必须这样做。
现在cc.yuerblog这个包里面创建一个class:
liangdongs-MacBook-Pro:mine liangdong$ cat src/cc/yuerblog/Demo.java package cc.yuerblog; public class Demo { }
这里package定义了该class所处的包名,稍后我会演示如何在其他class中引入这个Demo类,以及Java是如何基于classpath查找到这个Demo类的,让我们带着疑问继续向后。
现在创建一个包存放程序的入口文件App类,它包含main函数:
liangdongs-MacBook-Pro:mine liangdong$ cat src/cc/yuerblog/entry/App.java package cc.yuerblog.entry; import cc.yuerblog.Demo; public class App { public static void main(String[] args) { new Demo(); } }
引入Demo类是按照了包名引入的,其路径相对于项目目录。
大家写JAVA上来就用IDE,貌似IDE很简单就可以运行程序了,但其实IDE背后是做了一些幕后工作的,下面我们手动来编译程序,以便理解JAVA包的查找方式。
我们再次看一下项目结构,现在处在项目根目录mine:
liangdongs-MacBook-Pro:mine liangdong$ find ./src ./src ./src/cc ./src/cc/yuerblog ./src/cc/yuerblog/entry ./src/cc/yuerblog/entry/App.java ./src/cc/yuerblog/Demo.java
源码都放在src目录下,每一个.java文件都需要编译为.class字节码文件。
我们可以先编译Demo.java,因为App.java依赖它:
liangdongs-MacBook-Pro:mine liangdong$ javac src/cc/yuerblog/Demo.java liangdongs-MacBook-Pro:mine liangdong$ ll src/cc/yuerblog/ total 16 -rw-r--r-- 1 liangdong staff 194 8 6 14:41 Demo.class -rw-r--r-- 1 liangdong staff 45 8 6 13:35 Demo.java drwxr-xr-x 4 liangdong staff 128 8 6 14:31 entry
.class文件默认生成在.java同目录。
接着编译App.java:
liangdongs-MacBook-Pro:mine liangdong$ javac ./src/cc/yuerblog/entry/App.java ./src/cc/yuerblog/entry/App.java:3: 错误: 找不到符号 import cc.yuerblog.Demo; ^ 符号: 类 Demo 位置: 程序包 cc.yuerblog ./src/cc/yuerblog/entry/App.java:7: 错误: 找不到符号 new Demo(); ^ 符号: 类 Demo 位置: 类 App 2 个错误
报错找不到Demo类。
怎么办呢?我们需要知道java是怎么查找包的:
JAVA是去CLASSPATH环境变量定义的各个目录下,查找包路径。
比如这里设置:CLASSPATH=/Users/liangdong/eclipse-workspace/mine/src的话,那么cc.yuerblog.Demo类就会在/Users/liangdong/eclipse-workspace/mine/src/cc/yuerblog/中查找Demo.java。
javac编译代码的时候,可以通过-cp参数指定CLASSPATH,这样javac就可以找到依赖的Demo.java文件,并将其也编译成Demo.class:
javac -cp ./src/ ./src/cc/yuerblog/entry/App.java
项目所有的.class文件都编译出来之后,就可以把它们运行起来。
运行过程还是从App.class开始作为入口,后续调用到了Demo.class,因此我们运行程序得时候依旧需要classpath:
liangdongs-MacBook-Pro:mine liangdong$ java -cp ./src cc.yuerblog.entry.App
运行程序得时候,需要指定入口程序得完整类名,即:cc.yuerblog.entry.App。
因为指定了-cp ./src,所以可以在src/cc/yuerblog/entry/中找到App.class。
没有classpath则肯定无法找到./src下面的类:
liangdongs-MacBook-Pro:mine liangdong$ java cc.yuerblog.entry.App 错误: 找不到或无法加载主类 cc.yuerblog.entry.App
一般来说我们不会通过export CLASSPATH的方式设置包查找路径,因为会影响到其他java程序得包查找路径。
仅仅编译出那么多.class文件还不够,我们一般会将.class打包成一个jar包,这样其他项目可以引入我们的包并访问其中的类,或者直接运行我们包的main函数拉起程序。
jar包其实就是用zip压缩的一个包含了.class文件的目录。
我们稍微改造一下javac命令,把.class文件生成到一个独立的bin目录下:
liangdongs-MacBook-Pro:mine liangdong$ mkdir bin liangdongs-MacBook-Pro:mine liangdong$ javac -d ./bin -cp ./src/ ./src/cc/yuerblog/Demo.java ./src/cc/yuerblog/entry/App.java liangdongs-MacBook-Pro:mine liangdong$ find ./bin/ ./bin/ ./bin//cc ./bin//cc/yuerblog ./bin//cc/yuerblog/entry ./bin//cc/yuerblog/entry/App.class ./bin//cc/yuerblog/Demo.class
可见,.class按照包路径被放到了bin目录下。
如果我们写另外一个java类想要导入cc.yuerblog.Demo类,其实只需要在classpath中增加bin目录即可加载成功。
为了生成一个方便运输的Jar包,我们必须进入bin目录,此时相对路径才与包名一致,然后jar打包所有的.class文件:
jar -cvf mine.jar ./cc/yuerblog/Demo.class ./cc/yuerblog/entry/App.class 已添加清单 正在添加: cc/yuerblog/Demo.class(输入 = 194) (输出 = 166)(压缩了 14%) 正在添加: cc/yuerblog/entry/App.class(输入 = 308) (输出 = 236)(压缩了 23%)
我们可以解压一下jar文件看一下:
liangdongs-MacBook-Pro:bin liangdong$ unzip mine.jar -d ./x Archive: mine.jar creating: ./x/META-INF/ inflating: ./x/META-INF/MANIFEST.MF inflating: ./x/cc/yuerblog/Demo.class inflating: ./x/cc/yuerblog/entry/App.class
会发现jar包里有META-INF的元信息描述,以及所有按包名存放的class文件。
因为App.class具备main方法,我们可以指定jar包的入口程序,这样jar包就是可执行的了(-e参数):
jar -cvfe mine.jar cc.yuerblog.entry.App ./cc/yuerblog/Demo.class ./cc/yuerblog/entry/App.class
现在运行jar包会从cc.yuerblog.entry.App类执行:
liangdongs-MacBook-Pro:bin liangdong$ java -jar mine.jar
如果jar包是作为一个类库给其他人用,那么就不必指定入口类了。
假设我们新建一个项目,定义如下java类:
import cc.yuerblog.Demo; public class Test { public static void main(String[] args) { new Demo(); } }
编译的时候,需要通过-cp指定classpath指向jar包(jar包就是一个压缩的目录),那么java自然会在jar包的cc/yuerblog/Demo.class找到类文件:
liangdongs-MacBook-Pro:mine liangdong$ javac -cp ./bin/mine.jar Test.java
这样生成Test.class后,我们运行的时候不仅需要指定classpath包含Test.class所在目录,还需要包含jar:
liangdongs-MacBook-Pro:mine liangdong$ java -cp ./bin/mine.jar:. Test
javac的参数是文件名,并通过-cp指定对依赖的查找路径。 java的参数是类名,并通过-cp指定对依赖的查找路径。
java程序运行需要确保所有的.class文件都可以通过classpath找到,因此-cp参数兼具了:
如果文章帮到了你,请您乐于扫码捐赠1元钱,以便支持服务器运转。