第十章 代码的编译
对于编译来说有三种场景:
1、对于java语言来说编译就是把.java文件变成.class文件的过程。(前端编译器)
2、指虚拟机在后端运行期编译器JIT把字节码转变成机器码的过程。(即时编译器JIT)
3、使用静态提前编译器AOT编译器,直接把.java文件编译成本地机器码的过程。(静态提前编译器AOT)
最为我们熟悉应该是第一种前端编译器javac,它不依赖于虚拟机。是独立存在。
一、javac前端编译器
javac是于java语言实现的程序。
javac的编译过程大概分为三步:
1)解析与填充符号表
2)注解处理
3)分析与字节码生成
1、解析与填充符号表
这个过程包括编译原理里面词法分析和语法分析两个过程。
词法分析:是将源代码的字符流转变为标记(token)集合。关键字、变量名、字面量、运算符都可以成为标记。
如 int a = b + 2 这句话包含了6个标记。 int 、a 、 = 、 b、 +、 2
词法分析过程由com.sun.tools.javac.parser.Scanner类来实现
语法分析:根据token序列构造抽象语法树的过程。
抽象语法树(AST)是一种用来描述程序代码语法结构的树形表示方法。
表达式: 1+3*(4-1)+2
经过这个步骤之后,编译器就基本不会再对源码文件进行操作了。后续的操作都建立在抽象语法树上。
2、填充符号表
是由一组符号地址和符号信息构成的表格。
3、注解处理器
jdk1.5之后提供了对注解的支持。这里就是这理这些注解对语法树的修改。直到没有对语法树的修改为止。
4、语义分析和字节码生成
语义分析的主要任务是对结构上正确的源程序进行上下文有关性质的审查。
1)标注检查
检查的内容包括变量使用前是否已被声明、变量与赋值之间的数据类型是否能够匹配等
int a =2;
boolean b = false;
char c = 2;
int d = b +c;
2) 数据及控制流分析
检查的内容包括:程序的局部变量在使用前是否有赋值、方法的每条路径是否有返回值、是否受查异常都被正确的处理等问题
3)解语法糖
语法糖是为了为程序开发提供便利,在原有语法的基础上进行了某种封装。但其实底层实现没变。
常见的语法糖有:泛型、变长参数、自动装箱/拆箱、foreach这些都是
4)字节码生成
com.sun.tools.javac.jvm.Gen类来完成。不光把前面的语法树、符号表转化成字节码写到磁盘中,编译器还进行了少量的代码添另和转换。
比如<init>()默认的构造函数。<clinit>()静态方法块
还有字段串的加法变成stringBuffer或StringBuilder进行。
最后由com.sun.javac.jvm.ClassWriter输出字节码,生成最终的.class文件。
二、语法糖
我们先来下载下工具Java反编译工具-JD-GUI
https://github.com/java-decompiler/jd-gui/releases
1)泛型擦除
public void test1(){
Map<String,String> map = new HashMap<String, String>();
map.put("hello","你好");
map.put("how are you","吃了吗");
System.out.println(map.get("hello"));
}
public void test2(){
Map map = new HashMap();
map.put("hello","你好");
map.put("how are you","吃了吗");
System.out.println((String) map.get("hello"));
}
先编译为.class文件。然后用jd-gui打开class文件
我们发现,这两种写法是一样的效果。
2)自动拆装箱
public void openCase(){
Integer num1 = 123;//将一个基本数据类型赋给Integer对象
int num2 = num1;//将一个Integer对象赋给整形变量
}
打开之后: