使用命令来编译、运行、打包 Java 程序(java, javac, jar)

前一阵子需要把一个原本在 Windows 环境下的 Java Eclipse 项目放到 Linux 环境下测试,由于不想在 Linux 虚拟机中装 Eclipse,于是索性打算用命令来运行测试该项目。结果花了半小时,终于把项目跑起来了。 :sweat: :sweat: :sweat:

事后实验总结了一下,于是有了这篇博文。

带 package 包名 Java 程序的编译和运行

首先我们来新建一个测试类 HelloJava。

package com.thomastian.test;
public class HelloJava
{
    public static void main(String args[])
    {
        System.out.println("Hello, Java");
    }
}

直接编译(不推荐)

$ javac HelloJava.java

带目录结构的编译

$ javac -d . HelloJava.java

带选项的 javac

加上了 -d 选项后在当前目录下建立了对应包层次的目录结构(如上图)。如果没有加 -d,则在当前目录下生成 class 文件,需手动建立目录结构。

在运行时,需要加上主类对应的目录层次,可以采用包形式“.”,或者目录的形式“/”,这样 JVM 就会根据目录层次来查找对应的 class 文件并执行。执行目录必须是包顶级层次的上级目录(在这里就是 com 的上级目录“~”了)。

$ java com.thomastian.test.HelloJava
$ java com/thomastian/test/HelloJava

Java的归档(jar)

Java 使用 jar 命令打包(创建归档文件)。(jar 命令的选项跟 tar 命令是相同的,毕竟都是打包归档类的命令。关于 tar 命令可以看这篇博客园文章

这里前面的“HelloJava.jar”对应包的名称,后面的“com”对应的包的根目录。

$ jar cvf HelloJava.jar com

运行 jar:

$ java -jar HelloJava.jar

此时会提示错误:

错误提示

用解压软件打开 jar 包文件,可以看到里面有一个 MAINFEST.MF 文件。

MAINFEST.MF 文件

Manifest-Version: 1.0
Created-By: 1.8.0_65 (Oracle Corporation)

这是一个在归档时自动生成的文件,它是 JAR 档案文件中的特殊文件,用来定义扩展或档案打包相关数据。

如果 JAR 文件被用作可执行的应用程序,那么应用程序需要告诉 Java 虚拟机入口点位置,任意包含 public static void main(String[] args) 方法的类即为入口点。该信息由文件头 Main-Class 提供,基本格式如下:

Main-Class: classname

以上内容摘自维基百科条目:Manifest 资源配置文件,详细讲解了 Manifest 文件的用法以及相关资料。

接下来我们创建一个文本文件 Mainfest(随便什么名字都行),在文件中添加 Main-Class 值,注意冒号后面有空格,不然会报错,然后保存。

Manifest-Version: 1.0
Created-By: 1.8.0_65 (Oracle Corporation)
Main-Class: com.thomastian.test.HelloJava

在 jar 命令加上 f 选项,即可指定自定义的 Mainfest 文件,在包名的后面加上刚才创建的文件名“mainfest”。在运行完命令时,mainfest 文件的内容就会赋给 jar 包中的 MAINFEST.MF 文件,完成了 MAINFEST 文件的自定义。

$ jar cvfm HelloJava.jar mainfest com

带 f 选项的 jar 命令

生成 jar 包后,再执行 java -jar HelloJava.jar 命令即可成功运行。在设定了 jar 的文件关联后甚至可以双击运行,十分方便。

上面说过,被用作可执行的应用程序的 jar 包才需要指定 Main-Class,有些包是供其它程序调用的类库,并没有 main 方法,下文的 ImportClass 类就是这样。

jar包的引用

上文讲的是如何创建 jar 包,接下来我们来看看包的引用。

首先我们创建一个 ImportClass 类,用于引用。注意待引用的类必须为 public 类型,否则对外是不可见的,这样也就无法引用了。

package blog.thomastian.test;
public class ImportClass{
    public void print()
    {
        System.out.println("oh, my import class!");
    }
}

按照上文的方法将其编译为 jar 包。由于该文件没有 main 方法,所以也就无需指定 Main-Class 了。

修改前面的 HelloJava 类,引用并调用上面的 ImportClass 类。

package com.thomastian.test;
import blog.thomastian.test.ImportClass;
public class HelloJava
{
    public static void main(String args[])
    {
        ImportClass ic = new ImportClass();
        ic.print();
    }
}

编译:

$ javac -d . -cp lib/ImportClass.jar  HelloJava.java

运行:

$ java -cp .:lib/ImportClass.jar com.thomastian.test.HelloJava

运行结果

多个路径之间用“:”(Linux) / “;”(Windows)隔开(跟系统环境变量的写法相同)。在编译的时候 -cp 路径可以不包含当前路劲,但是在运行的时候必须要加上代表当前路径的“.”,具体可参考这篇stackoverflow问答

上篇新的一年,新的博客
下篇JavaScript 的 prototype(原型)和类