Cover image

有关Javac的探究

Nov 10, 2015

用惯了IDE式的一键式编程,初次转到命令行中多少还是有些不适应的。 但IDE有IDE的方便,直接编译还是有着直接编译的好处的。毕竟是可以更深层次的接触的编译的过程。

对于javac,发现并没有多少书来解释这个命令,通过javac -help能看到一堆对参数的说明。

Usage: javac <options> <source files>
where possible options include:
  -g                         Generate all debugging info
  -g:none                    Generate no debugging info
  -g:{lines,vars,source}     Generate only some debugging info
  -nowarn                    Generate no warnings
  -verbose                   Output messages about what the compiler is doing
  -deprecation               Output source locations where deprecated APIs are used
  -classpath <path>          Specify where to find user class files and annotation processors
  -cp <path>                 Specify where to find user class files and annotation processors
  -sourcepath <path>         Specify where to find input source files
  -bootclasspath <path>      Override location of bootstrap class files
  -extdirs <dirs>            Override location of installed extensions
  -endorseddirs <dirs>       Override location of endorsed standards path
  -proc:{none,only}          Control whether annotation processing and/or compilation is done.
  -processor <class1>[,<class2>,<class3>...] Names of the annotation processors to run; bypasses default discovery process
  -processorpath <path>      Specify where to find annotation processors
  -parameters                Generate metadata for reflection on method parameters
  -d <directory>             Specify where to place generated class files
  -s <directory>             Specify where to place generated source files
  -h <directory>             Specify where to place generated native header files
  -implicit:{none,class}     Specify whether or not to generate class files for implicitly referenced files
  -encoding <encoding>       Specify character encoding used by source files
  -source <release>          Provide source compatibility with specified release
  -target <release>          Generate class files for specific VM version
  -profile <profile>         Check that API used is available in the specified profile
  -version                   Version information
  -help                      Print a synopsis of standard options
  -Akey[=value]              Options to pass to annotation processors
  -X                         Print a synopsis of nonstandard options
  -J<flag>                   Pass <flag> directly to the runtime system
  -Werror                    Terminate compilation if warnings occur
  @<filename>                Read options and filenames from file

看着都眼晕,不过我们只需要对某几个Option来关注就可以。

@argfiles

话说用了这么久的javac还头一次知道有这么个option,在javac编译的时候我们可以不必每次都要 敲那么长的一串命令,可以先通过把编译的option或是sourcefiles写入到一个文件中然后通过@来编译。 比如说,我们把javac的参数放到options文件中,把sourcefile放到source文件中,类似下边例子:

#Options
-d ../bin/
-g
-sourcepath .
#srcFiles
testA.java
testB.java

之后,通过命令javac @Options @srcFiles来编译文件。

-classpath 与 -sourcepath

classpath 设定java要搜索的类的路径,即编译sourcefile所需要依赖的class的路径。

sourcepath 设定java要搜索类的源码的路径,即编译sourcefile所需要依赖的class的源码的路径。

一开始并不明白sourcepath的意义,以为是需要编译的源码的路径。后来看了看文档以及相关资料,才知道 sourcepath与classpath其实都是指要编译文件需要搜寻的依赖类的路径。只不过classpath指明的是已经编译好的 .class文件的位置,而sourcepath指明的是依赖的class的源码的位置,如果javac发现不存在或者现在的.class 不是最新版文件,会重新编译依赖文件来产生class文件。

我们可以通过-verbose来查看编译过程。

$javac -verbose -sourcepath . testB.java
[解析开始时间 RegularFileObject[testB.java]]
[解析已完成, 用时 14 毫秒]
[源文件的搜索路径: .]
[类文件的搜索路径: /lib/jvm/jdk7/jre/lib/resources.jar,/lib/jvm/jdk7/jre/lib/rt.jar,/lib/jvm/jdk7/jre/lib/sunrsasign.jar,/lib/jvm/jdk7/jre/lib/jsse.jar,/lib/jvm/jdk7/jre/lib/jce.jar,/lib/jvm/jdk7/jre/lib/charsets.jar,/lib/jvm/jdk7/jre/lib/jfr.jar,/lib/jvm/jdk7/jre/classes,/lib/jvm/jdk7/jre/lib/ext/zipfs.jar,/lib/jvm/jdk7/jre/lib/ext/sunpkcs11.jar,/lib/jvm/jdk7/jre/lib/ext/localedata.jar,/lib/jvm/jdk7/jre/lib/ext/dnsns.jar,/lib/jvm/jdk7/jre/lib/ext/sunec.jar,/lib/jvm/jdk7/jre/lib/ext/sunjce_provider.jar,.,/lib/jvm/jdk7/lib.tools.jar,/home/yeoman123/Workspace/java/MOECPM,/home/yeoman123/Workspace/java/public/colt-1.2.0.jar,/home/yeoman123/Workspace/java/public/slf4j-api-1.7.12.jar]
[正在加载ZipFileIndexFileObject[/lib/jvm/jdk7/lib/ct.sym(META-INF/sym/rt.jar/java/lang/Object.class)]]
[正在加载ZipFileIndexFileObject[/lib/jvm/jdk7/lib/ct.sym(META-INF/sym/rt.jar/java/lang/String.class)]]
[正在检查testB]
[正在加载ZipFileIndexFileObject[/lib/jvm/jdk7/lib/ct.sym(META-INF/sym/rt.jar/java/lang/AutoCloseable.class)]]
[正在加载ZipFileIndexFileObject[/lib/jvm/jdk7/lib/ct.sym(META-INF/sym/rt.jar/java/lang/System.class)]]
[正在加载ZipFileIndexFileObject[/lib/jvm/jdk7/lib/ct.sym(META-INF/sym/rt.jar/java/io/PrintStream.class)]]
[正在加载ZipFileIndexFileObject[/lib/jvm/jdk7/lib/ct.sym(META-INF/sym/rt.jar/java/io/FilterOutputStream.class)]]
[正在加载ZipFileIndexFileObject[/lib/jvm/jdk7/lib/ct.sym(META-INF/sym/rt.jar/java/io/OutputStream.class)]]
[正在加载RegularFileObject[./testA.java]]
[解析开始时间 RegularFileObject[./testA.java]]
[解析已完成, 用时 1 毫秒]
[已写入RegularFileObject[testB.class]]
[正在检查testA]
[已写入RegularFileObject[./testA.class]]
[共 307 毫秒]

其中文件testB.java里边对testA.java里的类进行调用,然而并没有事先编译testA.java文件,而是指定了sourcepath为 当前目录,我们可以看到编译进程中首先找到testA.java对其进行编译生成testA.class,然后再对testB.java进行编译。

需要注意的是:sourcepath的默认路径与classpath路径相同,但如果指定了之后会将原路径覆盖。

-d

-d选项设定了类文件的目标目录,即你编译出来的class文件所存放的位置,这里是指存放的根目录。什么意思呢?如果你的文件中没有进行打包(package) 那么直接就把class文件生成到该目录下边。如果文件中声明了package的信息,那么就会以-d后的path为根目录生成整个package的文件目录中。举个例子

package cn.edu.school.group.project;
public class testA{
    //...
}
$ javac -d /home/user/java/bin/ testA.java

之后会将testA.class生成到/home/user/java/bin/cn/edu/school/group/project中。如果想要import该类,则需要指定 该类的根目录,即-cp /home/user/java/bin即可。例如我在testBimport

import cn.edu.school.group.project.testA;
public class testB{
    public testB{
        System.out.println("This is B Initialize");
    }
    public static void main(String[] args){
        testA ta = new testA();
    }
}
$ javac -d /home/user/java/bin -cp /home/user/java/bin testB.java

如果不指定cp的话就会编译出错,或者将该目录添加到环境变量中其实也可以,但如果每个project都把classpath添加到环境变量中, 那环境变量会变得相当臃肿。

另外,关于classpath的搜索规则,参考@haolujun 大神的说明,java的类的寻找规则是这样子的:

class文件所在目录=Classpath+'\'+Package路径中'.'换成'\'

debug 选项

-g选项为编译提供了调试信息,包括行号信息,源文件信息以及局部变量信息。缺省情况只生成行号以及源文件信息。

-g:none 不生成任何调试信息。

-g:{keylist} 指定生成调试信息的类型,keylist包括source提供源文件调试信息,lines提供行号调试信息,以及 vars提供局部变量调试信息。类型之间用逗号来分隔。

如果你不想生成任何调试信息的话,需要-g:none来指定,不指定的话,即使不加-g默认也会生成行号以及源文件信息的。


10 Nov 2015

Post by: MetaCoder