Java:java、javac 、jar 命令


#Java 工具#


本文示例基于 Java 8 。

1、实战示例

1.1、入门示例

在当前目录下创建文件 HelloWorld.java ,内容如下:

public class HelloWorld {

    public static void main(String[] args) {
        System.out.println("你好,lt");
    }

}

此时是无法执行的:

$ java HelloWorld
错误: 找不到或无法加载主类 HelloWorld

需要先编译,然后去执行 class 文件:

$ javac HelloWorld.java

$ ls
HelloWorld.class HelloWorld.java

$ java HelloWorld
你好,lt

将class文件打包到 jar:

$ jar -cvf hello.jar HelloWorld.class
已添加清单
正在添加: HelloWorld.class(输入 = 425) (输出 = 303)(压缩了 28%)

此时当前目录会出现 hello.jar:

$ ls
HelloWorld.class HelloWorld.java  hello.jar

查看 hello.jar 中文件:

$ jar -tf hello.jar
META-INF/
META-INF/MANIFEST.MF
HelloWorld.class

$ unzip -q -c hello.jar META-INF/MANIFEST.MF
Manifest-Version: 1.0
Created-By: 1.8.0_191

执行 hello.jar 中的 HelloWorld 类:

$ java -cp hello.jar HelloWorld
你好

由于没有在 jar 中指定主类,若直接用-jar执行 hello.jar 会报错:

$ java -jar hello.jar
hello.jar中没有主清单属性

manifest.txt 内容(注意,最后一行后面一定要有回车换行):

Manifest-Version: 1.1
Created-By: lt
Main-Class: HelloWorld

更新 META-INF/MANIFEST.MF :

$ jar uvfm hello.jar ./manifest.txt

$ unzip -q -c hello.jar META-INF/MANIFEST.MF
Manifest-Version: 1.1
Created-By: lt
Main-Class: HelloWorld

换一种方式打包:

$ jar -cvfe hello2.jar HelloWorld HelloWorld.class

$ unzip -q -c hello2.jar META-INF/MANIFEST.MF
Manifest-Version: 1.0
Created-By: 1.8.0_191
Main-Class: HelloWorld

$ java -jar hello2.jar
你好,lt

再换一种方式打包:

$ jar -cvfm hello.jar manifest.txt HelloWorld.class
$ java -jar hello.jar
你好,lt

$ jar -cvmf manifest.txt hello.jar HelloWorld.class
$ java -jar hello.jar
你好,lt

1.2、示例:多个类的打包和执行

当前目录下文件结构:

$ tree
.
└── src
    └── com
        └── example
            ├── HelloWorld.java
            └── Util.java

Util.java 内容:

package com.example;

public class Util {

    public static int add(int a, int b) {
        return a + b;
    }
}

HelloWorld.java 内容如下:

package com.example;

public class HelloWorld {
    public static void main(String[] args) {
        System.out.println("1+2=" + Util.add(1, 2));
    }
}

打包方式1

编译 java 代码:

$ cd src

$ javac com/example/Util.java

$ javac com/example/HelloWorld.java

注意,如果是src作为路径,会报错:

$ javac src/com/example/Util.java

$ javac src/com/example/HelloWorld.java
src/com/example/HelloWorld.java:6: 错误: 找不到符号
        System.out.println("1+2=" + Util.add(1, 2));
                                    ^
  符号:   变量 Util
  位置: 类 HelloWorld
1 个错误

如果只编译 com/example/HelloWorld.javacom/example/Util.java 也会被自动编译。因为 HelloWorld 依赖 Util 。

查看编译结果:

$ tree
.
└── src
    └── com
        └── example
            ├── HelloWorld.class
            ├── HelloWorld.java
            ├── Util.class
            └── Util.java

打jar包方式1:

$ cd src

$ jar cvf hello.jar com/example/Util.class com/example/HelloWorld.class
已添加清单
正在添加: com/example/Util.class(输入 = 250) (输出 = 202)(压缩了 19%)
正在添加: com/example/HelloWorld.class(输入 = 683) (输出 = 417)(压缩了 38%

$ cd ..

$ tree 
.
└── src
    ├── com
    │   └── example
    │       ├── HelloWorld.class
    │       ├── HelloWorld.java
    │       ├── Util.class
    │       └── Util.java
    └── hello.jar
$ java -cp src/hello.jar com.example.HelloWorld
1+2=3

打jar包方式2:

$ cd src

$ jar cvf hello.jar **/*.class
已添加清单
正在添加: com/example/HelloWorld.class(输入 = 683) (输出 = 417)(压缩了 38%)
正在添加: com/example/Util.class(输入 = 250) (输出 = 202)(压缩了 19%)

$ cd ..

$ java -cp src/hello.jar com.example.HelloWorld
1+2=3

打包方式2

将编译结果放在一个单独的目录:

$ tree .
.
├── output
└── src
    └── com
        └── example
            ├── HelloWorld.java
            └── Util.java

$ javac -sourcepath src -d output **/*.java

$ tree .
.
├── output
│   └── com
│       └── example
│           ├── HelloWorld.class
│           └── Util.class
└── src
    └── com
        └── example
            ├── HelloWorld.java
            └── Util.java

$ jar cvf hello.jar -C output .
已添加清单
正在添加: com/(输入 = 0) (输出 = 0)(存储了 0%)
正在添加: com/example/(输入 = 0) (输出 = 0)(存储了 0%)
正在添加: com/example/HelloWorld.class(输入 = 683) (输出 = 417)(压缩了 38%)
正在添加: com/example/Util.class(输入 = 250) (输出 = 202)(压缩了 19%)

$ jar tf hello.jar
META-INF/
META-INF/MANIFEST.MF
com/
com/example/
com/example/HelloWorld.class
com/example/Util.class

1.3、示例:多个类、清单文件、资源文件的打包和执行

目录结构:

$ tree .
.
├── manifest.txt
├── output
│   └── com
│       └── example
│           ├── HelloWorld.class
│           └── Util.class
├── resource
│   ├── META-INF
│   │   └── MANIFEST.MF
│   └── test.txt
└── src
    └── com
        └── example
            ├── HelloWorld.java
            └── Util.java
    
$ cat manifest.txt
Manifest-Version: 1.1
Created-By: lt
Main-Class: HelloWorld

$ cat resource/META-INF/MANIFEST.MF
Manifest-Version: 1.0
Created-By: 1.8-TEST

$ cat resource/test.txt
abc

方式1

$ jar cvf hello.jar -C output .  -C resource .
已添加清单
正在添加: com/(输入 = 0) (输出 = 0)(存储了 0%)
正在添加: com/example/(输入 = 0) (输出 = 0)(存储了 0%)
正在添加: com/example/HelloWorld.class(输入 = 683) (输出 = 417)(压缩了 38%)
正在添加: com/example/Util.class(输入 = 250) (输出 = 202)(压缩了 19%)
正在忽略条目META-INF/
正在忽略条目META-INF/MANIFEST.MF
正在添加: test.txt(输入 = 3) (输出 = 5)(压缩了 -66%)

$ unzip -q -c hello.jar META-INF/MANIFEST.MF
Manifest-Version: 1.0
Created-By: 1.8.0_191

$ unzip -q -c hello.jar test.txt
abc

resource 目录中的 META-INF/MANIFEST.MF 没有被打包进去。

方式2

$ jar cvfm hello.jar ./resource/META-INF/MANIFEST.MF -C output .  -C resource .
已添加清单
正在添加: com/(输入 = 0) (输出 = 0)(存储了 0%)
正在添加: com/example/(输入 = 0) (输出 = 0)(存储了 0%)
正在添加: com/example/HelloWorld.class(输入 = 683) (输出 = 417)(压缩了 38%)
正在添加: com/example/Util.class(输入 = 250) (输出 = 202)(压缩了 19%)
正在忽略条目META-INF/
正在忽略条目META-INF/MANIFEST.MF
正在添加: test.txt(输入 = 3) (输出 = 5)(压缩了 -66%)

$ unzip -q -c hello.jar META-INF/MANIFEST.MF
Manifest-Version: 1.0
Created-By: 1.8-TEST

通过 m 选项指定 ./resource/META-INF/MANIFEST.MF ,将 MANIFEST.MF 放了进去。

方式3

$ jar cvfm hello.jar ./manifest.txt -C output .  -C resource .
已添加清单
正在添加: com/(输入 = 0) (输出 = 0)(存储了 0%)
正在添加: com/example/(输入 = 0) (输出 = 0)(存储了 0%)
正在添加: com/example/HelloWorld.class(输入 = 683) (输出 = 417)(压缩了 38%)
正在添加: com/example/Util.class(输入 = 250) (输出 = 202)(压缩了 19%)
正在忽略条目META-INF/
正在忽略条目META-INF/MANIFEST.MF
正在添加: test.txt(输入 = 3) (输出 = 5)(压缩了 -66%)

$ unzip -q -c hello.jar META-INF/MANIFEST.MF
Manifest-Version: 1.1
Created-By: lt
Main-Class: HelloWorld

将 manifest.txt 的内容复制到了 jar 包的 META-INF/MANIFEST.MF 中。

方式4

$ jar cvfM hello.jar ./manifest.txt -C output .  -C resource .
正在添加: manifest.txt(输入 = 60) (输出 = 62)(压缩了 -3%)
正在添加: com/(输入 = 0) (输出 = 0)(存储了 0%)
正在添加: com/example/(输入 = 0) (输出 = 0)(存储了 0%)
正在添加: com/example/HelloWorld.class(输入 = 683) (输出 = 417)(压缩了 38%)
正在添加: com/example/Util.class(输入 = 250) (输出 = 202)(压缩了 19%)
正在添加: META-INF/(输入 = 0) (输出 = 0)(存储了 0%)
正在添加: META-INF/MANIFEST.MF(输入 = 43) (输出 = 42)(压缩了 2%)
正在添加: test.txt(输入 = 4) (输出 = 6)(压缩了 -50%)

参数M表示不自动生成 META-INF/MANIFEST.MF 。所以将resource 中的 META-INF/MANIFEST.MF 打包进来。

1.4、示例:依赖第三方库的打包和执行

以 guava 为例,在 https://github.com/google/guava/releases 下载 jar。

目录结构:

$ tree .
.
├── lib
│   └── guava-31.1-jre.jar
└── src
    └── com
        └── example
            └── HelloGuava.java

错误的编译方式:

$ javac -sourcepath ./src -d ./output **/*.java
src/com/example/HelloGuava.java:4: 错误: 程序包com.google.common.collect不存在
import com.google.common.collect.Lists;
                                ^
src/com/example/HelloGuava.java:8: 错误: 找不到符号
        List<String> list = Lists.newArrayList("a", "b");
                            ^
  符号:   变量 Lists
  位置: 类 HelloGuava
2 个错误

应该用 -cp 指定依赖的jar:

$ javac -cp lib/guava-31.1-jre.jar -sourcepath ./src -d ./output **/*.java

$ tree
.
├── lib
│   └── guava-31.1-jre.jar
├── output
│   └── com
│       └── example
│           └── HelloGuava.class
└── src
    └── com
        └── example
            └── HelloGuava.java

打包:

$ jar cvf hello.jar -C output .

执行:

$ java -cp lib/guava-31.1-jre.jar:hello.jar com.example.HelloGuava
[a, b]

$ java -cp lib/guava-31.1-jre.jar:output com.example.HelloGuava
[a, b]

2、命令详解: jar 命令

jar 参数

$ jar
用法: jar {ctxui}[vfmn0PMe] [jar-file] [manifest-file] [entry-point] [-C dir] files ...
选项:
    -c  创建新档案
    -t  列出档案目录
    -x  从档案中提取指定的 (或所有) 文件
    -u  更新现有档案
    -v  在标准输出中生成详细输出
    -f  指定档案文件名
    -m  包含指定清单文件中的清单信息
    -n  创建新档案后执行 Pack200 规范化
    -e  为捆绑到可执行 jar 文件的独立应用程序
        指定应用程序入口点
    -0  仅存储; 不使用任何 ZIP 压缩
    -P  保留文件名中的前导 '/' (绝对路径) 和 ".." (父目录) 组件
    -M  不创建条目的清单文件
    -i  为指定的 jar 文件生成索引信息
    -C  更改为指定的目录并包含以下文件
如果任何文件为目录, 则对其进行递归处理。
清单文件名, 档案文件名和入口点名称的指定顺序
与 'm', 'f' 和 'e' 标记的指定顺序相同。

示例 1: 将两个类文件归档到一个名为 classes.jar 的档案中:
       jar cvf classes.jar Foo.class Bar.class
示例 2: 使用现有的清单文件 'mymanifest' 并
           将 foo/ 目录中的所有文件归档到 'classes.jar' 中:
       jar cvfm classes.jar mymanifest -C foo/ .

参数 f 对应 jar 文件名,m 对应 manifest 文件名,e对应入口类的全路径名(例如Hello、com.example.Hello)。文件名顺序一定要和m、f、e参数顺序一致。 下面两个是等价的。

jar cvfm classes.jar mymanifest -C foo/ .
jar cvmf mymanifest classes.jar -C foo/ .

ctxui 这几个参数不能同时出现。

更新 manifest

$ jar uvfm <jar-name>.jar <manifest-file-path>

示例:

$ jar -cvf hello.jar HelloWorld.class

$ jar tf hello.jar
META-INF/
META-INF/MANIFEST.MF
HelloWorld.class

$ unzip -q -c hello.jar META-INF/MANIFEST.MF
Manifest-Version: 1.0
Created-By: 1.8.0_191

manifest.txt 文件内容:

Manifest-Version: 1.1
Created-By: lt
Main-Class: HelloWorld

manifest.txt 文件名换成其他名字也可以的。

更新操作:

$ jar uvfm hello.jar ./manifest.txt
... java.util.jar.Attributes read
WARNING: Duplicate name in Manifest: Manifest-Version.
Ensure that the manifest does not have duplicate entries, and
that blank lines separate individual sections in both your
manifest and in the META-INF/MANIFEST.MF entry in the jar file.
... java.util.jar.Attributes read
WARNING: Duplicate name in Manifest: Created-By.
Ensure that the manifest does not have duplicate entries, and
that blank lines separate individual sections in both your
manifest and in the META-INF/MANIFEST.MF entry in the jar file.
已更新清单

$ unzip -q -c hello.jar META-INF/MANIFEST.MF
Manifest-Version: 1.1
Created-By: lt

注意,Main-Class 没有被补充进去。这是因为 manifest.txt 最后一行没有回车。

查看jar中文件列表

$ jar tf <jar-name>.jar

示例:

$ jar tf hello.jar
META-INF/
META-INF/MANIFEST.MF
com/
com/example/
com/example/HelloWorld.class
com/example/Util.class

将指定目录中的 class 文件放入 jar 中

$ jar cvf <jar-name>.jar -C <dir-path> .

示例:

$ tree .
.
├── output
│   └── com
│       └── example
│           ├── HelloWorld.class
│           └── Util.class
└── src
    └── com
        └── example
            ├── HelloWorld.java
            └── Util.java

将 output 中文件放入 jar 中:

$ jar cvf hello.jar -C output .
已添加清单
正在添加: com/(输入 = 0) (输出 = 0)(存储了 0%)
正在添加: com/example/(输入 = 0) (输出 = 0)(存储了 0%)
正在添加: com/example/HelloWorld.class(输入 = 683) (输出 = 417)(压缩了 38%)
正在添加: com/example/Util.class(输入 = 250) (输出 = 202)(压缩了 19%)

$ jar tf hello.jar
META-INF/
META-INF/MANIFEST.MF
com/
com/example/
com/example/HelloWorld.class
com/example/Util.class

将 manifest 和 class 一起打包

示例:

jar -cvfm hello.jar ./manifest.txt **/*.class

-C 参数

例如:

jar cvf hello.jar -C output .

表示临时切换到 output 目录,再执行不带 -C 参数的 jar 命令。output 后面的. 代表相对 output 的当前目录。

同时打包 class 、资源文件

示例:

jar cvf hello.jar X1.class X1.class X3.png

3、命令详解:javac 命令

编译指定目录下的所有 java 文件

比如目录是src,使用 javac -sourcepath src **/*.java 即可。 示例:

$ tree .
.
└── src
    └── com
        └── example
            ├── HelloWorld.java
            └── Util.java

$ javac -sourcepath src **/*.java

$ tree .
.
└── src
    └── com
        └── example
            ├── HelloWorld.class
            ├── HelloWorld.java
            ├── Util.class
            └── Util.java

一次编译多个文件

示例:

$ javac com/example/HelloWorld.java com/example/Util.java

编译结果放到指定目录

$ tree .
.
├── output
└── src
    └── com
        └── example
            ├── HelloWorld.java
            └── Util.java

$ javac -sourcepath src -d output **/*.java

$ tree .
.
├── output
│   └── com
│       └── example
│           ├── HelloWorld.class
│           └── Util.class
└── src
    └── com
        └── example
            ├── HelloWorld.java
            └── Util.java

4、命令详解释:java 命令

-cp 参数

-cp 全称-classpath,代表 目录和 zip/jar 文件的类搜索路径。 多个路径使用分隔符连接,Windows使用分号;,Linux下使用冒号:。 使用通配符时,不能添加.jar扩展名。

示例:

$ tree
.
├── lib
│   └── guava-31.1-jre.jar
├── output
│   └── com
│       └── example
│           └── HelloGuava.class
└── src
    └── com
        └── example
            └── HelloGuava.java

$ jar cvf hello.jar -C output .

$ java -cp lib/guava-31.1-jre.jar:hello.jar com.example.HelloGuava
[a, b]
$ java -cp "lib/*:hello.jar" com.example.HelloGuava
[a, b]

jar 与 class 混合

下面的示例中 HelloGuava 没有打包到 jar 中。

$ tree
.
├── lib
│   └── guava-31.1-jre.jar
├── output
│   └── com
│       └── example
│           └── HelloGuava.class
└── src
    └── com
        └── example
            └── HelloGuava.java

$ java -cp lib/guava-31.1-jre.jar:output com.example.HelloGuava
[a, b]

5、其他

查看 jar 中某个文件的内容

例如查看 test.jar 中 META-INF/MANIFEST.MF 文件内容。

unzip -q -c test.jar META-INF/MANIFEST.MF

解压 jar 到指定目录

示例:

unzip hello.jar -d dest/ 

( 本文完 )