Javac工作原理分析(3):语义分析器

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能够识别的字节码。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值