1、概述
1.1、编译器的分类
前端编译器:Sun的Javac、 Eclipse JDT中的增量式编译器(ECJ)[1]。 把*.java文件转变成*.class文件
JIT编译器:HotSpot VM的C1、 C2编译器。是指虚拟机的后端运行期编译器 ,把字节码转变成机器码
AOT编译器:GNU Compiler for the Java(GCJ)[2]、 Excelsior JET[3] 。静态提前编译器 ,直接把*.java文件编译成本地机器代码
1.2、编译期优化的方向
虚拟机设计团队把对性能的优化集中到了后端的即时编译器中,这样可以让那些不是由Javac产生的Class文件(如JRuby、 Groovy等语言的Class文件)也同样能享受到编译器优化所带来的好处。
但是Javac做了许多针对Java语言编码过程的优化措施来改善程序员的编码风格和提高编码效率。
相当多新生的Java语法特性,都是靠编译器的“语法糖”来实现,而不是依赖虚拟机的底层改进来支持,
可以说,Java中即时编译器在运行期的优化过程对于程序运行来说更重要,而前端编译器在编译期的优化过程对于程序编码来说关系更加密切。
所以,本章后面主要是介绍编译器执行过程和语法糖。
2、Javac编译器
javac本身就是一个由Java语言编写的程序 ,源码存放在JDK_SRC_HOME/langtools/src/share/classes/com/sun/tools/javac
从SunJavac的代码来看,编译过程大致可以分为3个过程,分别是:
解析与填充符号表过程。
插入式注解处理器的注解处理过程。
分析与字节码生成过程。
这3个步骤之间的关系与交互顺序如图10-4所示。
Javac编译动作的入口是com.sun.tools.javac.main.JavaCompiler类,上述3个过程的代码逻辑集中在这个类的compile()和compile2()方法中 。代码详略。
2.1、解析与填充符号表
1.词法、 语法分析
词法分析是将源代码的字符流转变为标记(Token)集合,单个字符是程序编写过程的最小元素,而标记则是编译过程的最小元素 。
在Javac的源码中,语法分析过程由com.sun.tools.javac.parser.Parser类实现,这个阶段产出的抽象语法树由com.sun.tools.javac.tree.JCTree类表示,
经过这个步骤之后,编译器就基本不会再对源码文件进行操作了,后续的操作都建立在抽象语法树之上。
2.填充符号表
填充符号表的过程由com.sun.tools.javac.comp.Enter类实现
2.3 注解处理器
2.4 语义分析与字节码生成
1.标注检查
2.数据及控制流分析
3.解语法糖
4.字节码生成
3、Java语法糖的味道
3.1 泛型与类型擦除
Java的泛型在编译时是把泛型替换了的,专业名字叫“泛型擦除”,所以在编译后是指向同一种类型的,也就是说ArrayList<int>与ArrayList<String> 是同一个类。
所以,对泛型重载,是不可以的。
void fun(ArrayList<int>)
void fun(ArrayList<String>)
这是会报错的。
但,如果两个函数的返回值不同,又不可以执行,这是java在一定程度上的妥协,但结果却未被了重载本身的要素,不能通过返回值去指派一个方法调用。
所以,java的泛型设计是被很多人吐槽的。
3.2 自动装箱、 拆箱与遍历循环
自动装箱是自动加了 Integer.valueOf()之类的方法
循环遍历是自动添加了迭代器。
3.3 条件编译
在C/C++中有#if来控制是否参与编译,但在java里为何没有?
原因是不需要。
因为Java语言天然的编译方式(编译器并非一个个地编译Java文件,而是将所有编译单元的语法树顶级节点输入到待处理列表后再进行编译,因此各个文件之间能够互相提供符号信息)无须使用预处理器。
虽然不需要,但java里也可以模拟条件编译,编译器在一些特定条件下可以把一些分支给优化掉,条件就是if(true)的else分支。
4 实战:插入式注解处理器
未懂。