第十章 早起(编译期)优化
一 Javac编译器
编译过程大致可以分为三个过程:解析与填充符号表的过程;插入式注解处理器的注解处理过程;分析与字节码生成过程。
1. 解析与填充符号表
解析过程由parseFiles()方法完成,包含经典编译原理中的词法分析和语法分析。词法分析是将源代码的字符流转变为标记(Token)集合。语法分析是根据Token序列构造抽象语法树的过程。抽象语法树(Abstract Syntax Tree,AST)是一种描述程序代码语法结构的树形式表现,每个节点代表程序中的语法结构(Construct),例如包、类型、修饰符、运算符、接口、返回值甚至代码注释都可以作为语法结构。
符号表(Symbol Table)是由一组符号地址和符号信息构成的表格。符号表中所登记的信息在编译的不同阶段都会用到,在语义分析中,用于语义检查和产生中间代码;在目标代码生成阶段,符号表是地址分配的。
2. 注解处理器
jdk1.6提供了插入式注解处理器标准API在编译期间对注解进行处理,可以读取、添加、修改抽象语法树的任意元素。处理注解期间对语法树进行修改,编译器将进入解析与填充符号表阶段重新处理。
3. 语义分析与字节码生成
语义分析的主要任务是对结构上正确的源程序进行上下文有关性质的审查,主要包括:标注检查,数据及控制流分析和解语法糖。
将前面各个步骤生成的信息(语法树、符号表)转换为字节码写到磁盘中,还进行了少量的代码添加和转换工作。代码添加:实例构造器和类构造器在这个阶段添加到语法树。代码转换:字符串加操作替换为StringBuffer或者StringBuilder。
二 Java语法糖
1. 泛型与类型擦除
泛型的本质是参数化类型(Parametersized Type)的应用,也就是说所操作的数据类型被指定为一个参数。
Java语言中的泛型只在源码中存在,在编译后的字节码中,就已经替换为原来的原生类型(Raw Type,也称为裸类型),所以泛型是java语言的一颗语法糖,Java语言中的泛型实现方法称为类型擦除,基于这种方法实现的泛型称为伪泛型。
例如Map<String,String> map = new HashMap<>();在编译成Class文件后再反编译,泛型类型都变回了原生类型。
另外,仅泛型不同其他参数类型都相同时,是无法重载的,因为进行擦除后,两种方法的特征签名会一模一样。但是擦除法所谓的擦除,仅仅是对方法的code属性中的字节码进行擦除,实际上元数据中还是保留了泛型信息,这也是我们能够通过反射手段获取到参数化类型的根本依据。
2. 自动装箱,拆箱与遍历循环
遍历循环把代码还原成了迭代器的实现,所以被遍历的类需要实现Iterable接口。
包装类的“==”运算在不遇到算出运算的情况下不会自动拆箱,包装类equals()方法不处理数据转型的关系。
看下面的例子:
public class B
{
public static void main(String[] args) {
Integer a = 1;
Integer b = 2;
Integer c = 3;
Integer d = 3;
Integer e = 321;
Integer f = 321;
Long g =3L;
System.out.println(c==d);//true
System.out.println(e==f);//false
System.out.println(c==(a+b));//true
System.out.println(c.equals(a+b));//true
System.out.println(g==(a+b));//true
System.out.println(g.equals(a+b));//false
}
}
3. 条件编译
Java进行条件编译的方式是使用条件为常量的if语句,例如:
public static void main(String[] args) {
if(true){
System.out.println("1");
}else{
System.out.println("2");
}
}
编译器会提示else部分为:dead code,并且编译Class文件后反编译的结果也不会有else部分的代码。