Java中jar包的创建和使用
jar包的基本概念
jar
包的全称是java archive
。jar
包本质就是一种压缩包。在Java开发中一般是用来压缩类的一个包。类似C/C++中的静态库和动态库,但是又不完全是。
C/C++中的静态库和动态库是对中间文件(*.o)打包成一个二进制包。
- 如果是静态库,则在编译可执行文件(*.exe)的时候把静态库和可执行文件编译到一起。
- 如果是动态库,则单独编译完可执行文件后,执行可执行文件时动态加载动态库。
Java的jar包是对编译好的字节码打包到一个jar包中。jar包分为以下两类:
- 不带主类
- 带主类
不带主类的jar包就和C/C++的库差不多了,带主类的jar包在运行的时候就需要使用jar.exe
来运行。
命令行生成jar包
不带主类
先把源文件写好,尽量使用简单点的例子,要不然容易把重心放到源文件的代码编写上了,我们这里的重点是讲jar包,不是源文件的具体功能。
我们以封装一个计算器类为例,然后把这个类打包供其他类使用。
在随便一个项目目录下创建cukor/calc
文件夹。然后创建MyCalc.java
文件,内容如下:
package cukor.calc;
public class MyCalc {
// 加法
public static int add(int... numbers) {
int sum = 0;
for (int i : numbers) {
sum += i;
}
return sum;
}
// 减法
public static int sub(int... numbers) {
int first = numbers[0];
for (int i = 1; i < numbers.length; i++) {
first -= numbers[i];
}
return first;
}
// 乘法
public static int mult(int... numbers) {
int result = 1;
for (int i : numbers) {
result *= i;
}
return result;
}
// 除法
public static int div(int... numbers) {
int first = numbers[0];
for (int i = 1; i < numbers.length; i++) {
try {
first /= numbers[i];
} catch(Exception e) {
System.out.println("除数不能为0,程序异常:" + e.getMessage());
}
}
return first;
}
}
上面的MyCalc
应该是没问题的,就分别封装了加、减、乘、除功能的四个静态成员函数,类外不用创建对象调用,直接使用类名来调用。
然后编写一个主类来调用它,回到项目目录,创建Main.java
文件,内容如下:
import cukor.calc.MyCalc; // 导入刚刚写好的类
public class Main {
public static void main(String[] args) {
int result;
result = MyCalc.add(1, 0, 3, 4);
printResult("加法", result);
result = MyCalc.sub(9, 32, 3, -10);
printResult("减法", result);
result = MyCalc.mult(4, 3, 2);
printResult("乘法", result);
result = MyCalc.div(4, 2, -1);
printResult("除法", result);
System.out.println("程序结束");
}
public static void printResult(String string, int result) {
System.out.println(string + result);
}
}
编译MyCalc.java
:
javac cukor/calc/MyCalc.java
编写Manifest文件:
自定义命名,但是要以.mf
为拓展名,如MyCalc.mf
Manifest-Version: 1.0
Class: cukor/calc/MyCalc
Create-By: 19
Manifest-Version: 1.0
:指定jar包的版本是1.0Class: cukor/calc/MyCalc
:指定jar包中包含的类Create-By: 19
:指定JDK版本
创建MyCalc.jar
:
jar -cfm MyCalc.jar MyCalc.mf cukor/calc/*.class
其中-cfm
:是-c
、-f
、-m
的结合简写,它们的解释可以在终端输入下面的命令查看:
jar --help
截取这几个选项的说明:
-c, --create 创建档案。通过 -f 或 --file 指定的档案文件名
包含路径时,还将创建
缺少的父目录
-f, --file=FILE 档案文件名。省略时, 基于操作
使用 stdin 或 stdout
--release VERSION 将下面的所有文件都放在
jar 的版本化目录中 (即 META-INF/versions/VERSION/)
-m, --manifest=FILE 包含指定清单文件中的
清单信息
MyCalc.jar
:生成的jar包的名字MyCalc.mf
:jar包清单文件,刚刚写好的cukor/calc/*.class
:实际打包的类,其中*
表示通配符,也可以指定全部名字
如果程序没有报错的话,查看当前项目目录应该得到下面的结果,如果报错了就检查一下那里不一样,并更改。
PS D:\myCoding\Terminal\Java\TestJar> ls
Directory: D:\myCoding\Terminal\Java\TestJar
Mode LastWriteTime Length Name
---- ------------- ------ ----
d---- 2023/4/16 星期日 12:31 cukor
-a--- 2023/4/16 星期日 12:38 526 Main.java
-a--- 2023/4/16 星期日 12:48 1334 MyCalc.jar
-a--- 2023/4/16 星期日 12:43 64 MyCalc.mf
如果使用压缩软件打开MyCalc.jar
会看到下面的结果,这里以Bandzip
为例。
这个时候可以把cukor
文件夹删掉。因为我们已经打包了,为了防止Main.java
编译的时候会依赖cukor
文件夹,所以我们需要把它删除或者移动到其他位置。
然后编译Main.java
:
javac -cp ".;MyCalc.jar" Main.java
然后运行Main.class
:
java -cp ".;MyCalc.jar" Main
-cp ".;MyCalc.jar"
:表示临时使用classpath
环境变量为:".;MyCalc.jar"
,jar包是一定要带上的要不然会报找不到类的错误。而且是编译和运行都要带上。如果依赖多个jar包就顺着写然后使用逗号分隔就可以了。例如".;MyCalc.jar;Test.jar;Test2.jar;"
.
输出结果:
加法8
减法-16
乘法24
除法-2
程序结束
如果Main.java
中使用了package
,那么在运行的时候一定要带上包名。
带主类
新建一个项目(新的文件夹下)。防止和上面的混乱。
上面的jar包是不带主类的,下面就把主类也打包到jar包中,然后使用jar
命令来运行,而不是用java
。
代码还是上面的,只是我们现在的MyCalc2.jar
要包含原来的MyCalc.class
和主类Main.class
。
可以把上一个项目的文件直接拷贝过来。然后用解压软件把MyCalc.jar
解压,然后只保留需要的cukor/calc/MyCalc.class
。路径也是保留的,要不然Main.java
编译不了。
编译Main.java
:
javac Main.java
如果报错了,说找不到类,那肯定是没有保留住cukor
文件夹及其子目录,因为Main.java
中导入包的代码是import cukor.calc.MyCalc;
,那包路径没有保留当然是报错的。
然后写Manifest文件(.mf
为后缀):
Manifest-Version: 1.0
Main-Class: Main
Create-By: 19
创建MyCalc2.jar
:
jar -cfm MyCalc2.jar manifest.mf Main.class cukor/calc/*.class
然后运行MyCalc2.jar
java -jar MyCalc2.jar
输出结果:
加法8
减法-16
乘法24
除法-2
程序结束
VSCode生成jar包
VSCode适合小型一点的java项目,大一点的项目需要不太合适,使用VSCode来封装jar包的过程中也可以学习到关于jdk命令行的一些小知识,虽然好像也没什么用,但是了解一下还是可以的。
首先需要在VSCode里安装Java拓展包插件:
然后创建Java项目:
项目类型:(一般项目即可)
然后跳出一个选择文件夹的框,选择一个文件路径就可以,这个是你的当前项目的路径。
项目结构:
.vscode
:放置项目属性设置,包括生成路径、classpath
的路径、jar
包的路径。bin
:字节码的输出路径lib
:jar
包的存放路径src
:项目源文件路径App.java
:默认生成的主类源文件Readme.md
:项目说明文档
可以打开Readme.md
查看当前项目结构的具体信息。
那现在开始写代码:其实还是用上面的代码。
- App.java
import cukor.calc.MyCalc; // 导入刚刚写好的类
public class App {
public static void main(String[] args) {
int result;
result = MyCalc.add(1, 0, 3, 4);
printResult("加法", result);
result = MyCalc.sub(9, 32, 3, -10);
printResult("减法", result);
result = MyCalc.mult(4, 3, 2);
printResult("乘法", result);
result = MyCalc.div(4, 2, -1);
printResult("除法", result);
System.out.println("程序结束");
}
public static void printResult(String string, int result) {
System.out.println(string + result);
}
}
- MyCalc.java
package cukor.calc;
public class MyCalc {
// 加法
public static int add(int... numbers) {
int sum = 0;
for (int i : numbers) {
sum += i;
}
return sum;
}
// 减法
public static int sub(int... numbers) {
int first = numbers[0];
for (int i = 1; i < numbers.length; i++) {
first -= numbers[i];
}
return first;
}
// 乘法
public static int mult(int... numbers) {
int result = 1;
for (int i : numbers) {
result *= i;
}
return result;
}
// 除法
public static int div(int... numbers) {
int first = numbers[0];
for (int i = 1; i < numbers.length; i++) {
try {
first /= numbers[i];
} catch(Exception e) {
System.out.println("除数不能为0,程序异常:" + e.getMessage());
}
}
return first;
}
}
VSCode不需要我手动编译,当我保存文件的时候自动帮我编译了。
那就直接生成jar
包吧。
通过控制面板找项目视图
左下角有项目视图
然后生成jar
包。
带主类的就选App
,不带主类就选<without main class>
。
现在选择第二个,第一个自己试了,都是一样的操作,只是等下生成的Manifest
文件的内容不一样而已,不一样的就一行。但打包到jar
包中的结果是一样的,就是选<without main class>
也会把App.class
包含进去。
打包成功VSCode右下角就出现下面的弹框。
在当前项目目录下就生成了TestCode.jar
,这个名字和项目名称相同。
然后可以删除或者移动cukor/calc/MyCalc.java
,把bin
目录下的文件都删除了。把TestCode.jar
移动到lib
文件夹下。
程序没有报错,并且重新自动编译好了新的App.class
。
直接运行,没有任何问题。
注意,这里不要自己手动运行,因为VSCode在自动帮我们编译的时候使用了--enable-view
选项,所以在运行的时候如果是我们自己手动运行,那么很容易漏掉这个选项,导致报错,而VSCode自带的运行就会自己加上这个选项所以不会报错。
其中'@C:\Users\ADMINI~1\AppData\Local\Temp\cp_ab0491xg1nsgvk4j00lx26h1u.argfile'
是一个超链接,会连接到本地的文件夹打开对应的文件,读取文件中的内容,并把内容替换这个超链接。
注意,这个超链接链接到的文件是在Temp
文件夹下的,所以其实可以随便删除,这个只是一个临时文件。
ctrl+鼠标左键
单击那个超链接,打开可以看到是下面的内容:
--enable-preview -XX:+ShowCodeDetailsInExceptionMessages -cp "D:\\myCoding\\Terminal\\Java\\Test\\TestCode\\bin;d:\\myCoding\\Terminal\\Java\\Test\\TestCode\\lib\\TestCode.jar"
除了路径和我的不一样之外,其他的应该是一样的。可以看到,VSCode在运行的时候传递给java.exe
的参数中就有刚刚提到的--enable-preview
。是因为我使用的JDK是19的版本,并不是一个长期支持的版本,所以就会有这个选项,其实如果在编译的时候不加这个--enable-preview
选项,也是可以的,但是在运行的时候就不能加。即对于--enable-preview
选项要么编译运行都加要么就都不要加。-XX:+ShowCodeDetailsInExceptionMessages
表示的是如果程序有什么异常的就会把对应的异常信息打印到控制台上。-cp
就是用来临时改变classpath
环境变量的,因为我们用到了我们自己的jar
包,所以一定要把我们的jar
包路径配置到classpath
环境变量中。
IDEA生成jar包
创建好项目后创建源文件。
- Main.java
package TestCode;
import TestCode.cukor.calc.MyCalc;
public class Main {
public static void main(String[] args) {
int result;
result = MyCalc.add(1, 0, 3, 4);
printResult("加法", result);
result = MyCalc.sub(9, 32, 3, -10);
printResult("减法", result);
result = MyCalc.mult(4, 3, 2);
printResult("乘法", result);
result = MyCalc.div(4, 2, -1);
printResult("除法", result);
System.out.println("程序结束");
}
public static void printResult(String string, int result) {
System.out.println(string + result);
}
}
- MyCalc.java
package TestCode.cukor.calc;
public class MyCalc {
// 加法
public static int add(int... numbers) {
int sum = 0;
for (int i : numbers) {
sum += i;
}
return sum;
}
// 减法
public static int sub(int... numbers) {
int first = numbers[0];
for (int i = 1; i < numbers.length; i++) {
first -= numbers[i];
}
return first;
}
// 乘法
public static int mult(int... numbers) {
int result = 1;
for (int i : numbers) {
result *= i;
}
return result;
}
// 除法
public static int div(int... numbers) {
int first = numbers[0];
for (int i = 1; i < numbers.length; i++) {
try {
first /= numbers[i];
} catch (Exception e) {
System.out.println("除数不能为0,程序异常:" + e.getMessage());
}
}
return first;
}
}
我当前的项目目录:
然后开始创建jar
包。
文件->项目结构->
工件->加号->JAR->来自具有依赖项的模块中。
找主类:
前面只是一堆设置,下面才是构建生成jar
包,IDEA比较麻烦。
然后在out
路径下就生成了Demo.jar
包。
但是,使用解压工具打开会发现,它把所有的软件包都打包进去了。。。。
第二次选择空项目。
然后同样的方式构建jar
包。但是下面要选择test.jar
.
然后生成了test.jar
.
使用解压软件打开看一下。
没有任何区别。然而更麻烦了。
好了,关于jar
包的创建和使用就说这么多,eclipse的就自己玩吧。同样的道理,其实理解了命令行版本的,图形化版本的都好理解。