10.1概述
Java语言的“编译期”其实是个“不确定”的过程:
前端编译器:把Java文件编译成class文件,例如Sun的javac、Eclipse JDT中的增量编译器(ECJ)
后端编译器(JIT编译器,just in time compiler):把字节码转换为机器码,例如HotSpotVM的C1、C2编译器
静态提前编译(AOT编译器,Ahead Of Time compiler):把Java直接编译为本地机器代码,例如:GNU Compiler for the Java(GCJ),Excelsior JET
本章提到的“编译器”和“编译期”指的是前端编译
javac做了许多针对Java语言编码过程的优化措施来改善程序员的编码风格和提高编码效率
即时编译器在运行期优化更重要,前端编译器对于程序编码来说关系更密切
10.2javac编译器
10.2.1Javac的源码和调试
编译的过程:
解析与填充符号表过程
插入式注解处理器的注解过程
分析与字节码生成过程
10.2.2解析与填充符号表
解析过程由parseFiles()方法完成,包含经典编译原理中的词法分析和语法分析
a>词法分析、语法分析
词法分析是将源代码的字符流转变为标记(Token)集合
词法分析由com.sun.tools.javac.parser.Scanner类实现
语法分析是根据Token序列构造抽象语法树的过程
语法分析由com.sun.tools.javac.parser.Parser类实现
抽象语法树(Abstract Syntax Tree,AST)是一种描述程序代码语法结构的树形式表现,每个节点代表程序中的语法结构(Construct),例如包、
类型、修饰符、运算符、接口、返回值甚至代码注释都可以作为语法结构
语法抽象树由java.sun.tools.javac.tree.JCTree表示
后续的操作都建立在语法树上
b>填充符号表
由enterTrees()方法完成
符号表(Symbol Table)是由一组符号地址和符号信息构成的表格
符号表中所登记的信息在编译的不同阶段都会用到,在语义分析中,用于语义检查和产生中间代码;在目标代码生成阶段,符号表是地址分配的
依据
过程由java.sun.tools.javac.comp.Enter类实现,出口是一个待处理列表
10.2.3注解处理器
注解与普通Java代码一样,在运行期发挥作用
jdk1.6提供了插入式注解处理器标准API在编译期间对注解进行处理,可以读取、添加、修改抽象语法树的任意元素
处理注解期间对语法树进行修改,编译器将进入解析与填充符号表阶段重新处理
初始过程由initProcessAnnotations()方法完成
10.2.4语义分析与字节码生成
语义分析的主要任务是对结构上正确的源程序进行上下文有关性质的审查
a>标注检查
变量使用前是否被声明
变量与赋值之间的数据类型是否匹配
...
b>数据及控制流分析
对上下文逻辑的进一步校验
可以检查出
局部变量在使用前是否赋值
方法的每条路径是否有返回值
是否所有的受检异常都正确处理
...
c>解语法糖
语法糖:计算机语言中添加的某种语法,这种语法对于语言的功能并没有影响,但是更方便程序员使用
语法糖能够增加程序的可读性,从而减少程序代码的出错机会
Java语言中语法糖主要有:泛型,变长参数,自动装箱/拆箱
解语法糖:虚拟机运行时不支持语法糖,它们在编译阶段还原回简单的基础语法结构
d>字节码生成
将前面各个步骤生成的信息(语法树、符号表)转换为字节码写到磁盘中,还进行了少量的代码添加和转换工作
代码添加:实例构造器和类构造器在这个阶段添加到语法树
代码转换:字符串加操作替换为StringBuffer或者StringBuilder
10.3Java语法糖的味道
语法糖不会提供实质性的功能改进,但是或许能够提高效率、提升语法严谨性、减少编码出错的机会
10.3.1泛型与类型擦除
泛型的本质是参数化类型(Parametersized Type)的应用,也就是说所操作的数据类型被指定为一个参数
Java语言中的泛型只在源码中存在,在编译后的字节码中,就已经替换为原来的原生类型(Raw Type,也称为裸类型),所以泛型是java语言的一颗语法糖,Java语言中的泛型实现方法称为类型擦除,基于这种方法实现的泛型称为伪泛型
引入Signature、LocalVariableTable等属性用于解决伴随泛型而来的参数化类型的识别
Signature:作用是存储一个方法在字节码层面的特征签名,保存的类型不是原生类型,而是包含了参数化类型的信息
擦除法所谓的擦除,仅仅是对方法的code属性中的字节码进行擦除,实际上元数据中还是保留了泛型信息,这也是我们能够通过反射手段获取到参数化类型的根本依据
10.3.2自动装箱、拆箱与遍历循环
自动装箱、拆箱在编译后转换成了对应的包装和还原方法
遍历循环把代码还原成了迭代器的实现,所以被遍历的类需要实现Iterable接口
包装类的“==”运算在不遇到算出运算的情况下不会自动拆箱
包装类equals()方法不处理数据转型的关系
10.3.3条件编译
Java进行条件编译的方式是使用条件为常量的if语句
10.4实战:插入式注解处理器
10.4.1实战目标
使用注解处理器API来编写一款拥有自己风格的校验工具,来检查命名规范
10.4.2代码实现
注解API知识点:
实现注解处理的代码需要继承抽象类javax.annotation.processing.AbstractProcessor
类中只有一个需要覆盖的抽象方法process(Set annotations,RoundEnvironment roundEnv),从第一个参数annotations获取注解处理器需要处理的注解集合,从第二个参数roundEnv中访问到当前这个Round中的语法树节点
processingEnv代表注解处理器框架提供的一个上下文环境
@SupportedAnnonationTypes:代表这个注解处理器对哪些注解感兴趣,*作为通配符表示所有注解
@SupportedSourceVersion:代表这个注解处理器可以处理哪个版本的java代码
每个注解处理器都是单例的
如果不需要修改或者生成语法树的内容,则process()方法返回false
10.4.3运行与测试
参考文献:
[1] 深入理解Java虚拟机 第二版 --周志明
网友评论