编译过程大致分为3个步骤,分别是:
解析主要包括词法分析和语法分析两个步骤
词法分析将源代码的字符流转变为标记集合,单个字符是程序编写过程的最小元素,而标记则是编译过程中最小元素,关键字,变量名,字面量,运算符都可以成为一个标记(Token)
语法分析是根据Token序列构造抽象语法树的过程,抽象语法树是一棵用来描述程序代码语法结构的树形表达式,每一个节点代表程序代码中一个语法结构例如包,类型,修饰符,返回值都是一个语法结构,经历这个步骤之后,编译器基本不会进行源码操作了,后续的操作都建立在抽象语法树之上
填充符号表
符号表是由一组符号地址和符号信息构成的表格,符号表中登记的信息在编译的不同阶段都要使用,在语义分析中,符号表登记内容将用于语义检查和产生中间代码,在目标代码生成阶段,当对符号名进行地址分配时,符号表是地址分配的依据
与普通代码一样,注解也是在运行期间发挥作用,通过插入式注解处理器可以读取,修改,添加抽象语法树中的任意元素,如果注解处理器对语法树进行了修改,编译器将回到解析及填充符号表的过程重新处理,直到注解处理器没有对语法树进行修改为止
语义分析与字节码生成
语法树能表示一个结构正确的源代码抽象,但无法保证源代码程序符合逻辑,而语法分析的主要任务是对结构上正确的源程序进行上下文有关性质的审查Javac 的编译过程中,语义分析过程分为标注检查一节数据控制流分析两个步骤
标注检查
标注检查内容包括变量使用前是否声明,变量与赋值之间类型是否匹配等,在标注检查中,还有一个过程称为常量折叠,如 int a = 1+2;在语法树上仍然能看到1,2以及操作符+,但经过常量折叠后,它们将折叠为3
数据及控制流分析
数据及控制流分析是对程序上下文逻辑更进一步的验证,可以检查出诸如程序局部变量在使用前是否赋值,方法的每个路径是否都有返回值,是否所有受查异常都被正确处理等问题,
解语法糖
语法糖指在计算语言中添加的某种语法,这种语法对语言的功能并没有任何影响,但更方便程序猿使用,通常来说语法糖能增加程序的可读性,从而减少代码出错机会常用的语法糖如:泛型,变长参数,自动装箱/拆箱等,虚拟机允许时不支持这种语法,它们是在编译阶段还原到简单的基础语法结构,这个过程称为解语法糖
字节码生成是Javac编译器过程的最后一个步骤,字节码生成阶段不仅仅将前面各个步骤所生成信息(语法树,符号表)转化为字节码写入磁盘,编译器还进行了少量的代码添加和转化工作例如init() 和 clinit() 方法就是这个阶段添加到语法树之中的,以及将字符串的加号操作进行StringBuffer或StringBuilder的append()操作替换等。
尽管语法糖不会提供实质性的功能改进,但是它们或能提高开发效率,或能提升语法的严谨性,或能减少代码出错的机会
泛型与类型擦除
泛型的本质是参数化类型的应用,也就是说操作的数据类型被指定为一个参数,这个参数类型可以用在类,接口和方法的创建中,分别对应的是泛型类,泛型方法和泛型接口
与C#泛型不同,Java语言中的泛型它只在程序源代码中存在,在编译后的字节码文件中,就已经替换为原来的原生类型,并在相应的地方插入了强制转换代码,因此对于Java来说,ArrayList(String)和 ArrayList(Integer)都是同一个类,Java中的泛型实现方法称为类型擦除
public static void main(String[] args){
Map map = new HashMap();
map.put("hello", "yss");
System.out.print(map.get("hello"));
}
在Java代码编译成Class文件,然后进行反编译之后等到
public static void main(String[] args){
Map map = new HashMap();
System.out.print((String)map.get("hello"));
}
map.put("hello", "yss");
类型的擦除,仅仅是对方法中Code属性字节码进行擦除,实际上元数据中还是保留了泛型信息,这也是我们能通过反射获取参数化类型的依据。当不可否认擦除的使用会带来某些场景的不足,如方法的重载
自动装箱,拆箱与遍历(Foreach)循环
//自动装箱,拆箱与循环遍历
public static void main(String[] args){
List list = Arrays.asList(1,2,3,4);
int sum = 0;
for(int i : list) sum += i;
System.out.print(sum);
}
编译之后
// 反编译之后代码
public static void main(String[] args){
List list = Arrays.asList(new Integer[]{
Integer.valueOf(1),
Integer.valueOf(2),
Integer.valueOf(3),
Integer.valueOf(4)
});
int sum = 0;
for(Iterator localIterator = list.iterator(); localIterator.hasNext();){
int i = ((Integer) localIterator.next()).intValue();
sum += i;
}
System.out.print(sum);
}
在变长参数中,它在调用的时候变成一个数组类型的参数