提纲
概述
java编译器分类:
前端编译器:把*.java文件转变成*.class文件的过程
后端运行期编译器(JIT编译器):把字节码转变成机器码的过程
静态提前编译器(AOT编译器):把*.java文件编译成本地机器码的过程
javac编译器
编译过程:
解析与填充符号表
词法分析:将原代码的字符流转变为标记(Token)集合。程序编写的最小元素是单个字符,编译过程的最小元素是标记。关键字、变量名、字面量、运算符都可以成为标记。
语法分析:根据token序列构造抽象语法树的过程。抽象语法树是一种用来描述程序代码语法结构的树形表示方式。
填充符号表:是一组由符号地址和符号信息构成的表格。用于语义检查和产生中间代码。
注解处理器
可以读取、修改、添加抽象语法树中的任意元素。如果在注解期间对语法树进行了修改,编译器将回到解析及填充符号表的过程重新处理。
语义分析与字节码生成
标注检查:变量使用前是否已被声明、变量与赋值之间的数据类型是否匹配等。
数据以控制流分析:对程序上下文逻辑更进一步的验证,他可以检查诸如程序局部变量在使用前是否被赋值、方法的每一条路径是否都有返回值、是否所有的受查异常都被正确处理等问题。
解语法糖: 语法糖能够增加程序的可读性,从而减少程序代码出错的机会。java中最常用的语法糖主要是泛型、变长参数、自动装箱/拆箱等。它们在编译阶段还原会最简单的基础语法结构,称为解语法糖。
字节码生成:将其它信息转换为字节码,将实例构造器<init>()方法和类构造器<clinit>()方法添加到语法树中。
Java语法糖
泛型与类型擦除
泛型的本质是参数化类型的应用,也就是说所操作的数据类型被指定为一个参数。这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。
真实泛型:在系统运行期生成,有自己的虚方法表和类型数据,这种实现称为类型膨胀,基于这种方法实现的泛型称为真实泛型。
伪泛型:只在程序代码中存在,在编译后的字节码文件中,就已经替换为原来的原生类型,并且在相应的地方插入了强制转型代码。这种实现称为类型擦除,基于这种方法实现的泛型称为伪泛型。
擦除法的问题:List<String>与List<Integer>擦除后是同一个类型,在进行方法重载时参数会变成一样的原生类型List<E>,因此编译不会执行,但是添加两个不同的并不需要实际用到的返回值才能完成重载。(JDK1.6)
JDK1.7:1.7之前,只要描述符不同的两个方法就可以存在在同一class文件中,1.7之后对编译期做了检查,保证行为一致。故会拒绝编译。
自动拆箱、装箱与遍历循环
List<Integer> list = Arrays.asList(1, 2, 3, 4); int sum = 0; for (int i : list) { sum += i; } System.out.println(sum); |
List<Integer> list = Arrays.asList(newInteger[] { Integer.valueOf(1), Integer.valueOf(2), Integer.valueOf(3), Integer.valueOf(4) });//装箱 intsum = 0; for(Iterator localIterator = list.iterator(); localIterator.hasNext();)//遍历循环 { inti = ((Integer)localIterator.next()).intValue();//拆箱 sum += i; } System.out.println(sum); |
包装类的“==”运算在不遇到算术运算的情况下不会自动拆箱,以及它们equals方法不处理数据转型的关系,在实际编码中尽量避免这样使用自动拆箱与装箱。
条件编译
if(true){ System.out.println("block1"); }else{ System.out.println("block2"); } |
System.out.println("block1"); |
条件编译只能写在方法体内部,只能实现基本语句块级别的条件编译,没有办法实现根据条件调整整个类的结构。