一、javac.exe简介
javac.exe是java语言编译器。javac读取由java语言编写的类和接口的定义,并将它们编译成字节代码的class文件。
注意:
- javac.exe使用Java语言编写的,而不是C语言,至于如何将Java程序封装为.exe文件,可以参考下面的博客:
http://blog.youkuaiyun.com/kevinzhangfei/article/details/4569170 - javac.exe包括在JDK里面,但不是JVM中的内容,JVM只负责执行.class文件,而javac.exe负责将java文件编译为class文件。
二、源码下载
OpenJDK6源码: http://download.java.net/openjdk/jdk6/
Javac的源码就在OpenJDK源码里面。
或者在优快云下载: http://download.youkuaiyun.com/detail/p_3er/7383741
通过源码可以看到,javac其实就是一个java project。
三、javac中的包简介
将javac导入Eclipse,目录结构如下:
Javac的公共入口点是com.sun.tools.javac.main.Main。
- com.sun.tools.javac.code
描述java语言内在语义的类 – 类型types, 符号symbols等。 - com.sun.tools.javac.comp
用语义细节来分析和标注语法分析树, 比如确定标识符identifiers的类型和符号。 - com.sun.tools.javac.jvm
用于读写class files的后端类。 - com.sun.tools.javac.main
顶层的驱动类. 编译器的标准入口点是 com.sun.tools.javac.main.Main (more…) - com.sun.tools.javac.parser
读取java源文件并创建语法分析树的类。 - com.sun.tools.javac.resources
编译器产生的资源文件. 其中两个是由”属性文件编译器”从属性源文件中生成的。Compiler.properties and javac.properties; 第三个是在构建的时候自动产生的,保存版本信息version.properties。 - com.sun.tools.javac.tree
表示java语言的被标注的语法树的类. 最顶层的节点Tree.TopLevel表示源文件的内容(应该是JCTree.TopLevel)。 - com.sun.tools.javac.util
工具类, 提供调试、文件系统存取和javac的集合类的支持。
四、javac.exe编译过程
Java源码编译机制
Java 源码编译由以下三个过程组成:
- 分析和输入到符号表
- 注解处理
- 语义分析和生成class文件
流程图如下所示:
最后生成的class文件由以下部分组成:
- 结构信息
包括class文件格式版本号及各部分的数量与大小的信息。 - 元数据
对应于Java源码中声明与常量的信息。包含类/继承的超类/实现的接口的声明信息、域与方法声明信息和常量池。 - 方法信息
对应Java源码中语句和表达式对应的信息。包含字节码、异常处理器表、求值栈与局部变量区大小、求值栈的类型记录、调试符号信息。
五、使用javac.exe的一些细节
1、如果一个java源文件有package选项,比如
package com.ghs;
public class Test{
}
(1)执行javac Test.java命令,会在当前目录生成一个Test.class文件,不会创建..\com\ghs目录。
(2)执行javac –d . Test.java命令,会在当前目录创建子目录com\ghs,生成的Test.class文件在ghs目录中。
javac命令的-d选项的本义是指定存放生成的类文件的位置;它还有一个作用,就是如果java源文件中有包名,那么-d选项告诉编译器,把生成的类文件放入反映包名的子目录中去,必要时创建目录。
2、import
import的作用是简化书写,在编译的时候,编译器会帮助我们把类的全名补上来,import的功能就到这了,它并不会把别的文件的代码写进来。
所以说import和#include是不一样的,#include会把别的文件的内容导入进来,而import不会,import只是简化了我们的书写。
3、java的package 机制
基本原则:需要将类文件切实安置到其所归属之Package所对应的相对路径下。
例如:以下面程序为例:假设此Hello.java文件在D:\Java\下
package A;
public class Hello{
public static void main(String args[]){
System.out.println("Hello World!");
}
}
D:\Java>javac Hello.java 此程序可以编译通过。
D:\Java>java Hello 但是执行时,却提示以下错误!
Exception in thread "main" java.lang.NoClassDefFoundError: hello (wrong name: A/Hello)
at java.lang.ClassLoader.defineClass0(Native Method)
原因是我们把生成的Hello.class规定打包在D:\Java\A文件中,必须在A文件中才能去运行。所以应该在D:\Java目录下建立一个A目录,然后把把Hello.class放在它下面,执行时,可正常通过!
D:\Java>java A.hello 就会输出:Hello world!
(就算是进入A目录下,运行java Hello命令,也会提示找不到类,因为带包名的类,在运行时必须指定包名,即java 命名后面跟的是类的完全限定名)
4、我用的是jdk1.6。但我没有配置classpath环境变量。在运行javac命令的时候,这个版本的jdk会自动在下面的目录中寻找类。
这是我的源文件:
package com.ghs;
public class Test{
public static void main(String[] args){
System.out.println("hello");
}
}
比如我运行javac命令的时候,加上-verbose选项:
[search path for source files: .](默认会在当前路径下寻找java源文件)
[search path for class files: ](默认会在以下路径中寻找java源文件中用到的其它类的类文件,注意,这些路径都是在jdk中的jre目录下)
C:\jdk-1.6-64\jdk1.6.0_37\jre\lib\resources.jar,
C:\jdk-1.6-64\jdk1.6.0_37\jre\lib\rt.jar,
C:\jdk-1.6-64\jdk1.6.0_37\jre\lib\sunrsasign.jar,
C:\jdk-1.6-64\jdk1.6.0_37\jre\lib\jsse.jar,
C:\jdk-1.6-64\jdk1.6.0_37\jre\lib\jce.jar,
C:\jdk-1.6-64\jdk1.6.0_37\jre\lib\charsets.jar
C:\jdk-1.6-64\jdk1.6.0_37\jre\lib\modules\jdk.boot.jar,
C:\jdk-1.6-64\jdk1.6.0_37\jre\classes,
C:\jdk-1.6-64\jdk1.6.0_37\jre\lib\ext\dnsns.jar,
C:\jdk-1.6-64\jdk1.6.0_37\jre\lib\ext\localedata.jar,
C:\jdk-1.6-64\jdk1.6.0_37\jre\lib\ext\sunjce_provider.jar,
.(默认也会在当前路径中寻找)
]
[loading java\lang\Object.class(java\lang:Object.class)](load的类是在我写的java源文件中需要用的类;
还有一种情况,比如import 类名,那么这个类会被加载(就算是代码中没有用到);但是import 包名.*,这个包下面的类只有在代码中出现才会被加载。)
[loading java\lang\String.class(java\lang:String.class)]
[checking com.dyc.Test]
[loading java\lang\System.class(java\lang:System.class)]
[loading java\io\PrintStream.class(java\io:PrintStream.class)]
[loading java\io\FilterOutputStream.class(java\io:FilterOutputStream.class)]
[loading java\io\OutputStream.class(java\io:OutputStream.class)]
[wrote Test.class]