编译过程分为三个部分:
- 解析与填充符号表
- 插入式注解处理器的注解处理过程
- 分析和字节码的生成
解析与填充符号表
词法语法分析,生成抽象语法树(AST,Abstract Syntax Tree);
填充符号表
注解处理器
该部分可以操作上一步生成的语法树,修改一次,解析和填充符号表重新做一次,直到注解处理器再没有对语法树进行修改为止。
《深入理解Java虚拟机》本部分内容的案例分析,就是针对这部分内容,可见其重要程度。(对于本书的案例,读者将会在介绍完基本知识之后,找时间讲解)
语义分析与字节码生成
标注检查:重要的一个动作——常量折叠,int a = 1+2;经过常量折叠之后,直接存储int a = 3。
数据及控制流分析:局部变量没有访问标志等信息,则在方法内声明final信息,class文件是不会知道局部变量是否被声明了final,这就要靠编译期来保证。
解语法糖:语法糖,指在计算机语言中添加的某种语法,该语法对于语言功能没有影响,只是为了增加程序的可读性。常见的语法糖
- 泛型,如下代码:
public class Test{
public void fun(List<String> ls){}
public void fun(List<Integer> li){}
}
可这段代码是无法编译的,因为再解语法糖之后,class文件中就会擦除String和Integer信息,所以两个方法对于class文件来说完全一样,这是不被允许的。接着看:
public class Test{
public int fun(List<String> ls){}
public void fun(List<Integer> li){}
}
神奇的事情发生了,这段代码编译通过了,这是因为class文件判断两个方法相同不相同的根据是描述符,而上一节也介绍了描述符(参数列表+返回值),上面两个方法的描述符分别是:(Ljava/util/List)I,(Ljava/util/List)V,所以他么是可以在class文件*存。
2. 拆箱装箱
Integer a = 1;
Integer b = 2;
Integer c = a + b;
上述代码自动发生了拆箱来相加,自动装箱赋值给c。
3. 条件编译
public static void main(String[] args){
if(true){
System.out.println("block1");
}
else{
System.out.println("block2");
}
}
编译结果只有block1,if是特殊的,如果使用while(true),则编译的控制流分析中报错。
字节码生成:
不仅将前面各个步骤信息转换为字节码,还进行少量代码添加和转换工作。比如类构造器()和实例构造器(),这里的实例构造器不是默认构造器,如果代码中没有提供构造器,默认生成的构造器将在填充符号表时完成。 <cinit>()
就是将代码中的“static{}”收集到一块,其中赋值(static变量)语句在“static{}”之前执行。 <init>()
就针对“{}”和实例变量了。