概述
Javac编译器
javac的源码与调试
从javac代码的总体结构来看,编译过程大致可以分为1个准备过程和3个处理过程,它们分别是如下所示:
-
准备过程:初始化插入式注解处理器
-
解析与填充符号表过程,包括
- 词法、语法分析。将源代码的字符流转变为标记集合,构造出抽象语法树。
- 填充符号表。产生符号地址和符号信息
-
插入式注解处理器的注解处理过程:插入式注解处理器的执行阶段。
-
分析与字节码生成过程,包括
- 标注检查。对语法的静态信息进行检查。
- 数据流与控制流分析。对程序动态运行过程进行检查。
- 解语法糖。将简化代码编写的语法糖还原为原有的形式。
- 字节码生成。将前面各个步骤所生成的信息转化成字节码。
解析与填充符号表
-
词法、语法分析
词法分析说将源代码的字符流转变为标记(token)集合的过程,单个字符说程序编写时的最小元素,但标记才是编译时的最小元素。关键字、变量名、字面量、运算符都可以作为标记,入“int a = b + 2”这句代码就包含了6个标记。
语法分析说根据标记序列构造抽象语法树的过程,抽象语法树是一种用来描述程序代码语法结构的树形表示方式,抽象语法树的每一个节点都代表着程序代码中的一个语法结构,例如包、类型、修饰符、运算符、接口、返回值甚至连代码注释都可以是一种特定的语法结构。
-
填充符号表
符号表是由一组符号地址和符号信息构成的数据结构,可以把它类比想象成哈希表中键值对的存储形式(也可以是有序符号表、树状符号表、栈结构符号表等各种形式)。符号表中所登记的信息在编译的不同阶段都要被用到。
注解处理器
我们可以把插入式注解处理器看作是一组编译器的插件,当这些插件工作时,允许读取、修改、添加抽象语法树中的任意元素。如果这些插件在处理器注解期间对语法树进行修改,编译器将回到解析及填充符号表的过程重新处理,知道所有插入式处理器都没有再对语法树进行修改为止,每一次循环过程称为一个轮次,入上图所示。
语义分析与字节码生成
经过语法分析之后,编译器获得了程序代码的抽象语法树表示,抽象语法树能够表示一个结构正确的源程序,但无法保证源程序的语义上符合逻辑的。而语义分析的主要任务则是对结构上正确的源程序进行上下文相关性质的检查。
-
标注检查
javac在编译过程中,语义分析过程可分为标注检查和数据及控制流分析两个步骤。标注检查步骤是要检查的内容包括诸如变量使用前是否被声明、变量与赋值之间的数据类型是否能够匹配等等。在标注检查中,还会顺便进行一个称为常量折叠的代码优化,如a = 1 + 3,优化后为a = 3;
-
数据及控制流分析
数据流分析和控制流分析说对程序上下文逻辑更进一步的验证,它可以检查出诸如程序局部变量在使用前是否有赋值、方法的每条路径是否都有返回值、是否所有的受检查异常都被正确处理了等问题。
-
解语法糖
Java中常见的语法糖包括了前面提到过的泛型、变长参数、自动装箱拆箱等等。Java运行时并不直接支持这些语法,它们在编译阶段被还原回原始的基础语法结构,这个过程就称为解语法糖。
-
字节码生成
字节码生成阶段不仅仅是把前面各个步骤所生成的信息(语法树、符号表)转化成字节码指令写到磁盘中,编译器还惊醒了少量的代码添加和转换工作,如实例构造器()方法和类构造器()方法就是这个阶段被添加到语法树之中的。
Java语法糖
泛型
泛型的本质是参数化类型或者参数化多态的应用,即可以将操作的数据类型指定为方法签名中的一种特殊参数,这种参数类型能够在类、接口和方法的创建中,分别构成泛型类、泛型接口和泛型方法。
-
Java与C#的泛型
Java选择的泛型实现方式叫做“类型擦除式泛型”,而C#选择的泛型实现方式是“具现化泛型”。
-
类型擦除
Java的泛型是伪泛型,这是因为Java在编译期间,所有的泛型信息都会被擦掉,正确理解泛型概念的首要前提是理解类型擦除。Java的泛型基本上都是在编译器这个层次上实现的,在生成的字节码中是不包含泛型中的类型信息的,使用泛型的时候加上类型参数,在编译器编译的时候会去掉,这个过程成为类型擦除。例如:
List<String>和List<Integer>在编译后都变成 List。Signature是方法表的一个属性,它存储了一个方法在字节码层面的特征签名,这个属性中保存的参数类型并不是原生类型,而是包括了参数化类型的信息。擦除法所谓的擦除,仅仅是对方法的code属性中的字节码进行擦除,实际上元数据还是保留了泛型信息,这也是我们在编码是能通过反射手段获取参数化类型的根本依据。
-
泛型擦除与多态的冲突和解决方法
类型擦除后,父类的的泛型类型全部变为了原始类型Object,而子类的类型是Date,参数类型不一样,这如果实在普通的继承关系中,根本就不会是重写,而是重载。
JVM采用了一个特殊的方法,来完成这项功能,那就是桥方法。详见www.cnblogs.com/hongdada/p/…
自动装箱、拆箱与循环遍历
自动装箱 、拆箱在编译之后被转化成了对应的包装和还原方法,如本例中的Integer.valueOf()与Integer.intValue()方法,而循环遍历则是把代码还原成了迭代器的实现,这也是为何便利循环需要被遍历的类实现Iterable接口的原因。变长参数,它在调用的时候变成了一个数组类型的参数。
条件编译
Java语言中条件编译的实现,也是Java语言的一颗语言糖,根据布尔常量值的真假,编译器将会把分支中不成立的代码块消除掉,这一工作将在编译器解除语法糖阶段完成。
实战:插入式注解处理器
实战目标
代码实现
\