java代码经过词法分析器和语法分析器后形成一棵结构化、可造作的语法树,但是这棵语法树太粗糙了,离我们的目标java代码字节码的产生还有点差距。必须要在这棵语法树的基础上在做一些处理,如给类添加默认的构造函数,检查变量在使用前是否已经初始化,将一些常来进行合并处理,检查操作变量类型是否匹配,检查所有的操作语句是否可达,检查checked exception异常是否已经捕获或抛出,解除Java的语法糖,等等。
在一个类中除了类本身会定义一些符号变量如类名称、变量名称和方法名称等,还有一些符号是引用其他类的,这些符号会调用其他类的方法或者变量等,还有一些类可能会继承或者实现超类和接口等。这些符号都是在其他类中定义的,那么就需要将这些类的符号也解析到符号表中。然后按照递归向下的顺序解析语法树,将所有的符号都输入到符号表中。这些操作由com.sun.tools.javac.comp.Enter类分两步完成:
1)将在所有类中出现的符号输入到类自身的符号表中,所有类符号、类的参数类型符号(泛型参数类型)、超类符号和继承的接口类型符号等都存储到一个未处理的列表中
2)将这个未处理列表中所有的类都解析到各自的类符号列表中
下面来看一下具体操作。
1.如果没有构造函数,则添加无参构造函数
2.annotation处理(区分注解生命周期和作用范围?还有什么其他的操作欢迎一起来讨论)
3.检查语义的合法性并进行逻辑判断:
1)检查语法树中的变量类型是否正确,如二元操作符两边的操作数的类型是否匹配,方法返回的类型是否与接收的引用值类型匹配等等,由com.sun.tools.javac.comp.Check类辅助完成
2)检查变量、方法或者类的访问是否合法、变量是否是静态变量、变量是否已经初始化等,由Resolve类辅助完成
3)常量折叠,将一个字符串常量中的多个字符串合并成一个字符串,将数值常量计算出结果,由ConstFold类辅助完成
// 编译后会变成 int a = 2;
int a = 1 + 1;
//编译后会变成 String s = "helloworld"
String s = "hello" + "world";
4)推导泛型方法的参数类型等。由Infer类辅助完成
4.最后一步,由Flow类完成数据流分析:
1)检查变量在使用前是否都已经被正确赋值。除了java中的原始类型(int、long、byte等等)会有默认的初始化值外,其他想String类型和对象的引用都必须在使用前先赋值
2)保证final修饰的变量不会被重新赋值。如果重新赋值会在这一步编译报错,如果是静态变量,定义时就必须赋值
3)确定方法的返回值类型。检查方法的返回值类型是否确定,并检查接受这个方法返回值的引用类型是否匹配,如果没有返回值,则不能有任何引用类型指向方法的这个返回值
4)所有的Check Exception都要捕获或者向上抛出
5)所有的语句都要被执行到。这一步会消除无用的代码;去除永假的条件判断;解除语法糖;检查是否有语句出现在一个return方法的后面等等
// 这段代码编译时会被去掉
if(false) {
System.out.println("false");
}
// 编译前
public void test() {
int b = 1;
Integer in = 1;
b = in;
Long l = in + 2L;
}
// 编译后
public void test() {
// 不明白第一句是怎么出来的,但是把上面的 int b = 1 改成 int b就不会出现了
// 有可能是b在第三句会被重新赋值,第一句对b的赋值没有意义,就用一行占位代码替换掉,但感觉这样没必要
// 莫非是一个bug? 手动[滑稽]
boolean var1 = true;
Integer var2 = Integer.valueOf(1);
int var4 = var2.intValue();
Long var3 = (long)var2.intValue() + 2L;
}
// 编译前
public void test2() {
List<String> list = new ArrayList<>();
list.forEach(System.out::println);
for (String item : list) {
System.out.println(item);
}
int[] arr = {1,2,3};
for (int i : arr) {
System.out.println(i);
}
}
// 编译后
public void test2() {
ArrayList var1 = new ArrayList();
PrintStream var10001 = System.out;
System.out.getClass();
// stream API在编译的过程没有变化
var1.forEach(var10001::println);
List var2 = (List)var1.stream().map((var0) -> {
return var0 + "ss";
}).collect(Collectors.toList());
// list的forEach被转换成了iterator形式
Iterator var3 = var1.iterator();
while(var3.hasNext()) {
String var4 = (String)var3.next();
System.out.println(var4);
}
int[] var8 = new int[]{1, 2, 3};
int[] var9 = var8;
int var5 = var8.length;
// Array的forEach被传换成了普通for循环
for(int var6 = 0; var6 < var5; ++var6) {
int var7 = var9[var6];
System.out.println(var7);
}
}
// 编译前
public class Test {
int a = 1;
public void test() {
InnerClass innerClass = new InnerClass();
innerClass.b++;
}
class InnerClass {
int b = 1;
}
}
// 编译后
public class Test {
int a = 1;
public Test() {
}
public void test() {
Test$InnerClass var1 = new Test$InnerClass(this);
++var1.b;
}
}
// 内部类InnerClass被重命名为Test$InnerClass
// 创建了一个以Test类为参数的过构造函数,并且持有Test类的一个对象引用
class Test$InnerClass {
int b;
Test$InnerClass(Test var1) {
this.this$0 = var1;
this.b = 1;
}
}
java代码到这里,编译就工作就完成了一大半了,还剩下最后一步,转换成jvm能够识别的字节码。