我看Java虚拟机(4)---Javac编译器

时间:2022-12-27 15:19:33

编译过程分为三个部分:

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

解析与填充符号表

词法语法分析,生成抽象语法树(AST,Abstract Syntax Tree);
填充符号表

注解处理器

该部分可以操作上一步生成的语法树,修改一次,解析和填充符号表重新做一次,直到注解处理器再没有对语法树进行修改为止。
《深入理解Java虚拟机》本部分内容的案例分析,就是针对这部分内容,可见其重要程度。(对于本书的案例,读者将会在介绍完基本知识之后,找时间讲解)

语义分析与字节码生成

标注检查:重要的一个动作——常量折叠,int a = 1+2;经过常量折叠之后,直接存储int a = 3。
数据及控制流分析:局部变量没有访问标志等信息,则在方法内声明final信息,class文件是不会知道局部变量是否被声明了final,这就要靠编译期来保证。
解语法糖:语法糖,指在计算机语言中添加的某种语法,该语法对于语言功能没有影响,只是为了增加程序的可读性。常见的语法糖

  1. 泛型,如下代码:
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>()就针对“{}”和实例变量了。