前端编译与优化

将.java编译为.class

编译过程

initProcessAnnotations(processors, sourceFileObjects, classnames); // 插入注解处理器
...
processAnnotations( // 注解处理
    enterTrees(
        stopIfError(CompileState.PARSE,initModules(stopIfError(CompileState.PARSE, parseFiles(sourceFileObjects))))
    ),
    classnames
);
...
case BY_TODO: // 分析及字节码生成
    while (!todo.isEmpty())
          generate(desugar(flow(attribute(todo.remove()))));
    break;

准备过程:初始化插入式注解处理器

解析与填充符号表

词法、语法分析

将源代码的字符流转变为标记集合,构造出抽象语法树

如int a = b 解析出int,a,=,b四个符号

经过词法和语法分析生成语法树以后,编译器就不会再对源码字符流进行操作了,后续的操作都建立在抽象语法树之上

填充符号表

产生符号地址和符号信息

符号表中所登记的信息在编译的不同阶段都要被用到

注解处理

插入式注解处理器的执行阶段

可以把插入式注解处理器看作是一组编译器的插件,当这些插件工作时,允许读取、修改、添加抽象语法树中的任意元素。如果这些插件在处理注解期间对语法树进行过修改,编译器将回到解析及填充符号表的过程重新处理,直到所有插入式注解处理器都没有再对语法树进行修改为止

分析与字节码生成

语义分析的主要任务则是对结构上正确的源程序进行上下文相关性质的检查,譬如进行类型检查、控制流检查、数据流检查

标注检查

标注检查步骤要检查的内容包括诸如变量使用前是否已被声明、变量与赋值之间的数据类型是否能够匹配等等

这个阶段会对源代码做一个常量折叠的代码优化

数据流及控制流分析

数据流分析和控制流分析是对程序上下文逻辑更进一步的验证,它可以检查出诸如程序局部变量在使用前是否有赋值、方法的每条路径是否都有返回值、是否所有的受查异常都被正确处理了等问题

比如final局部变量的不可修改性就是在这个阶段进行保证

解语法糖

将简化代码编写的语法糖还原为原有的形式

字节码生成

将前面各个步骤所生成的信息转化成字节码

实例构造器<init>()方法和类构造器<clinit>()方法就是在这个阶段被添加到语法树之中的

Java 语法糖

泛型

Java 的泛型是在编译阶段解决的 也就说在运行阶段泛型的类型都被擦除了,所以以下几种用法都有问题

public class TypeErasureGenerics<E> {
  public void doSomething(Object item) {
    if (item instanceof E) { // 不合法,无法对泛型进行实例判断
      ...
    }
    E newItem = new E(); // 不合法,无法使用泛型创建对象
    E[] itemArray = new E[10]; // 不合法,无法使用泛型创建数组
  }
}

Java 之所以要在编译之后抹除泛型 很大的一个原因就是为了保证向后兼容 也就是高版本JVM可以运行低版本Class文件

由于Java 采用的是通过在某些地方插入类型转换字节码的方式来实现泛型,所以擦除泛型带来了一些问题:

  1. 无法支持原生类型 因为原生类型无法转换为Object
  2. 运行期无法获取到泛型类型,必须通过额外的手段(比如方法传入一个Class对象)

后来引入了诸如Signature、LocalVariableTypeTable等新的属性用于解决伴随泛型而来的参数类型的识别问题

自动装拆箱 遍历循环

public static void main(String[] args) {
  List<Integer> list = Arrays.asList(1, 2, 3, 4);
  int sum = 0;
  for (int i : list) {
    sum += i;
  }
  System.out.println(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.println(sum);
}

但还是需要注意自动装拆箱的陷阱 比如著名的Integer 1 = 1 问题

== 在不晕倒算术运算的情况下不会自动装拆箱 以及包装类的equals并不会处理转型问题

条件编译

使用常量作为条件 可以条件编译代码块 编译期编译期会把死代码消除掉

if (condition){
  ...
}

实战-自定义注解处理器

@SupportedAnnotationTypes("*")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class NameScanner extends AbstractProcessor {
    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        System.out.println(annotations);
        return false;
    }
}

运行时指定处理器:

javac -processor wang.ismy.jvm.annotation.NameScanner wang/ismy/jvm/Main.java