所谓编译:

 

 

虚拟机规范严格定义了Class文件的格式,但是《Java虚拟机规范(第二版)》中,虽然有专门的一章“Compiling for the Java Virtual Machine”,但都是举例来说明的,并没有对如何把Java源码转变成Class文件的编译过程做十分严格的规范,这导致Class文件编译在某种程度上是与具体实现相关的,在一些极端的情况下,可能出现一段代码Javac编译器可以编译,但是ECJ编译器就不可以编译通过。从Sun Javac 的代码来看编译过程大致可以分为3个过程,分别是:

 

 

  1. 解析与填充符号过程。
  2. 插入式注解处理器的注解处理过程。
  3. 分析与字节码生成过程。

 

 

 

这三个步骤之间的关系与交互顺序如图:

 

java编译时保留注释符号 javac编译 注解处理_Java

 一.解析与填充表

 

词法分析:

词法分析就是将源代码的字符流转变为标记集合,单个字符式程序编写过程的最小元素,而标记则是编译过程的最小元素,关键字,变量名,字面量,运算符都可以称为标记,如“int a=b+2”这句代码包含了6个标记,分别是int,a,=,b,+,2,虽然关键字int由三个字符构成,但是它只是一个Token,不可再拆分。

 

语法分析:

语法分析是由Token序列构成抽象语法树的过程,抽象语法树是一种用来描述程序代码语法结构的树形标识方式,语法树的每一个节点都代表着程序代码中的一个语法结构,例如包,类型,修饰符,运算符,接口,返回值甚至注释等都可以是一个语法结构。产出语法抽象树后,基本就不对源码进行操作了,后续操作都建立在抽象语法树之上。

 

填充符号表

完成了语法分析和词法分析后的下一步就是填充符号表的过程。符号表是由一组符号地址和符号信息构成的表格,读者可以把它想象成哈希表中K-V的形式。符号表中所登记的信息在编译的不同阶段都要用到。语义分析中符号表所登记的内容将用于语义检查和产生中间代码。在目标代码生成阶段,当对符号名进行地址分配时,符号表是地址分配的依据。

 

 

 

 

二.注解处理器

 

在JDK1.5后,Java语言提供了对注解的支持,这些注解与普通的Java代码一样,是在运行期间发挥作用的。在JDK1.6中实现了JSR-269规范,提供了一组插入式注解处理器的标准API在编译期间对主机进行处理,我们可以把它看作是一组编译器的插件,在这些插件里面,可以读取,修改,添加抽象语法树中的任意元素。如果这写插件在处理注解期间对语法树进行了修改,编译器将回到解析及填充符号表过程重新进行处理,知道所有插入式注解处理器都没有再对语法树进行修改为止,每一次循环为一个Round。

 

有了编译器注解处理的标准API后,我们的代码才有可能干涉编译器的行为,由于语法树中的任意元素,甚至包括代码注释都可以再插件之中访问到,所以通过插入式注解处理器实现的插件功能上有很大的发挥空间。

 

 

 

三.语义分析与字节码生成

 

语法分析之后,编译器获得了程序代码的抽象语法树表示,语法树能表示一个结构正确的源程序的抽象,但无法保证源程序是符合逻辑的。而语义分析的主要任务就是对结构正确的源程序进行上下文有关性质的审查,如进行类审查。

 

标注检查:

标注检查步骤检查的内容包括诸如变量使用前是否以被声明,变量与赋 值之间的数据类型是否能够匹配等。在标注检查阶段,还有一个中要的动作称为常量折 叠。如果我们在代码中定义为int a=1+2 我们在语法树上只能看到int a=3 这就是常量 折叠。

 

数据及控制流分析:

数据及控制流分析是对程序上下文逻辑更进一步的验证,他可 以检查出来注入局部变量在使用前是否赋值,方法的每条路径是否都有返回值,是否所 有受查异常都被正确处理了等问题。编译时期的数据及控制流分析的目的与类加载时的 数据控制流分析的目的基本上是一致的,但校验范围是有区别的。

 

解语法糖:

语法糖,也称为糖衣语法,指在计算机语言中添加的某种语法,这种语 法对语言的功能并没有影响,但是更方便程序员的使用。通常来说,使用语法糖能够增 加程序的可读性,从而减少代码出错的机会。Java在现代编译语言中属于“低糖语言”, 尤其是在JDK1.5之前,“低糖”语法也是Java语言被怀疑已经落后的表面理由。Java 中最常用的语法糖就是泛型,边长参数,自动拆/装箱等,虚拟机运行时不支持这些语 法,他们在编译阶段还原回简单的基本语法结构,这个过程称为解语法糖。

 

字节码生成:

字节码生成时Javac编译过程的最后一个阶段。字节码生成阶段不仅仅 是吧前面各个步骤所生成的信息(语法树,符号表)转化成字节码写到磁盘中,编译器 还进行了少量的代码添加工作。比如,向语法树中插入实例构造器<init>,类构造器 <clinit>(注意这里所说的实例构造器并不是指默认的构造器,默认的无参构造器在填 充符号阶段就已经完成了)。还有比如把字符床的加操作替换成StringBuffer或 StringBuilder的append()操作等。

 

 

完成对语法树的遍历和调整后,就会使用填充了所有信息的符号表生成最终的Class文件,至此,整个编译过程宣告结束。