12. 早期(编译期)优化

        Java 的 “编译器” 是一段 “不确定” 的操作过程,它可能是指一个前端编译器把 *.java 文件转换成 *.class 文件的过程;也可能是指虚拟机的后端运行期编译器(JIT 编译器,Just In Time Compiler)把字节码转换成机器码的过程;也可能是是指使用静态提前编译器(AOT 编译器,Ahead Of Time Compiler)直接把 *.java 文件编译成本地集器代码的过程。本文只针对第一种情况。

一、Javac 编译器

       它本身是一个由 Java 语言编写的程序。

1. Javac 的源码与调试

Javac 的源码存放在 JDK_SRC_HOME/langtools/src/share/classes/com/sun/tools/javac中。除了 JDK 自身的 API 外,就只引用了JDK_SRC_HOME/langtools/src/share/classes/com/sun/*里的代码。从 Sun Javac 的代码来看,编译过程大致分为三个过程:

  • 解析与填充符号表的过程
  • 插入式注解器的注解过程
  • 分析与字节码生成过程

12. 早期(编译期)优化

                                                                                 Javac 的编译过程

        Javac 的编译动作入口是com.sun.tools.javac.main.JavaCompiler类,上述的三个过程集中在这个类的 compile() 和 compile2()方法里。 

                      12. 早期(编译期)优化

                                                                            Javac 编译过程的主体代码

2. 解析与填充符号表

      解析步骤由上图中的 parseFiles() 方法完成,解析步骤包括词法解析和语法分析两个过程。

      2.1 词法、语法解析

        词法解析是将源码中的字符流转变为标记(Token)集合,单个字符是程序编写过程的最小元素,而标记则是编译过程的最小元素。关键字、变量名、字面量和运算符都可以成为标记。如 “int a = b + 2” 中包含了6个标记,分别为 int、a、=、b、+、2。在Javac 的源码中,词法分析过程由 com.sun.tools.javac.parser.Scanner 类来实现。

        语法解析是根据 Token 序列来构造抽象语法树的过程,抽象语法树(AST,Abstract Syntax Tree)是一种用来描述程序代码语法结构的树形表示形式,语法树的每一个节点都代表着程序代码的一个语法结构,例如包、类型、修饰符、运算符等。在Javac 源码中,词法分析过程由 com.sun.tools.javac.parse 类来实现,这个阶段产出的抽象语法树由 com.sun.tools.javac.tree.JCTree 类来表示。

        2.2 填充符号表

       由上图中 enterTrees() 方法实现。符号表是由一组符号地址和符号信息构成的表格。在语义分析中,符号表所登记的内容将用于语义检查和产生中间代码。在目标代码生成阶段,当对符号进行地址分配时,符号表是地址分配的依据。在 Javac 源码中,填充符号表过程由 com.sun.tools.javac.comp.Enter 类实现。

3. 注解处理器

       在 JDK1.5 之后 Java 提供了对注解的支持,在运行期间发挥作用。在Javac 源码中,插入式注解处理器的初始化过程是在 initPorcessAnnotations() 方法中完成的,这个方法判断是否有新的注解处理器需要执行,若有则通过 com.sun.tools.javac.processing.JavacProcessingEnvironment 类的 doProcessing() 方法生成一个新的 JavaCompiler 对象对编译的后续步骤进行处理。

4. 语义分析与字节码生成

       语义分析的主要任务是对结构上正确的源程序进行上下文有关性值的审查。字节码生成则是 Javac 编译的最后一个阶段,在 Javac 源码里由 com.sun.tools.javac.jvm.Gen类来完成。字节码不仅是将前面个步骤所生成的信息转换成字节码写到磁盘上,编译器还进行了少量的代码添加和转换工作。

二、 Java 语法糖

       语法糖是指在计算机语言中添加的某种语法,这种语法对语言的功能并没有影响,但是更方便程序员使用。

1. 泛型和泛型擦除

       泛型是 JDK1.5 的一种新特性,本质是参数化类型的应用。它可以用在类、方法、接口上。在 Java 还未支持泛型前,只能通过 Object 是所有类型的父类和类型强制转换的配合实现类型泛化。Java 的泛型与 C# 中的泛型有着根本性的区别:C# 中的泛型无论在源码,编译后、运行期间都是切实存在的,这种实现称为类型膨胀,基于这种方法实现的泛型称为真实泛型。而 Java 中的泛型只是在源码中存在,在编译后的字节码文件中,就被替换成了原生类型,并且在相应的地方插入了强制类型转换代码。因此,对于运行时的 Java 语言来说,ArrayList<Integer> 与 ArrayList<String> 是同一种类。 Java 中的泛型实现方法称为类型擦除,基于这种方法实现的泛型被称为伪泛型。

2. 自动装箱、自动拆箱和 Foreach 循环

         12. 早期(编译期)优化

                                                                                     反编译前

         12. 早期(编译期)优化

                                                                                    反编译后

3. 条件编译

        Java 使用条件为常量的 if 语句来实现条件编译。它也是 Java 中的一颗语法糖,根据布尔常量值的真假,编译器将会把分支中不成立的代码块消除掉,这一工作在编译器解除语法糖的阶段完成。

                    12. 早期(编译期)优化

     Java 语言还有不少其他的语法糖,比如内部类、枚举类、断言语句、对枚举和字符串的 switch 支持、在 try 语句中定义和关闭资源等。