编译过程

Javac 编译过程大致可以分为1个准备过程和3个处理过程:

  1. 准备过程:初始化插入式注解处理器。
  2. 解析与填充符号表过程,包括:
    1. 词法、语法分析,将源代码的字符流转变为标记集合,构造出抽象语法树。
    2. 填充符号表,产生符号地址和符号信息。
  3. 插入式注解处理器的注解处理过程。可以把插入式注解处理器看作是一组编译器的插件,当这些插件工作时,允许读取、修改、添加抽象语法树中的任意元素。如Lombok注解。
  4. 分析与字节码生成过程,包括:
    1. 标注检查。对语法的静态信息进行检查。
    2. 数据流及控制流分析。对程序动态运行过程进行检查。
    3. 解语法糖。将简化代码编写的语法糖还原为原有的形式。
    4. 字节码生成。将前面各个步骤所生成的信息转化成字节码。

上述3个处理过程里,执行插入式注解时又可能会产生新的符号,如果有新的符号产生,就必须转回到之前的解析、填充符号表的过程中重新处理这些新符号。javac编译过程如下:



图1-1 Javac 编译过程

注解处理器

JDK 5之后,Java语言提供了对注解(Annotations)的支持,注解在设计上原本是与普通的Java代码一样,都只会在程序运行期间发挥作用的。

但在JDK 6中又提出并通过了JSR-269提案,该提案设计了一组被称为“插入式注解处理器”的标准API,可以提前至编译期对代码中的特定注解进行处理

从而影响到前端编译器的工作过程。可以把插入式注解处理器看作是一组编译器的插件,当这些插件工作时,允许读取、修改、添加抽象语法树中的任意元素

如果这些插件在处理注解期间对语法树进行过修改,编译器将回到解析及填充符号表的过程重新处理,直到所有插入式注解处理器都没有再对语法树进行修改为止。

典型示例为Java著名的编码效率工具Lombok,它可以通过注解来实现自动产生getter/setter方法、进行空置检查、生成受查异常表、产生equals()和hashCode()方法等等。

要通过注解处理器API实现一个编译器插件,首先需要了解这组API的一些基本知识。实现注解处理器的代码需要继承抽象类javax.annotation.processing.AbstractProcessor,

这个抽象类中只有一个子类必须实现的抽象方法:process(),它是Javac编译器在执行注解处理器代码时要调用的过程,从这个方法的第一个参数“annotations”中获取到

此注解处理器所要处理的注解集合,从第二个参数“roundEnv”中访问到当前这个轮次(Round)中的抽象语法树节点,每个语法树节点在这里都表示为一个Element。

除了process()方法的传入参数之外,还有一个很重要的实例变量processingEnv,它是AbstractProcessor中的一个protected变量,在注解处理器初始化的时候(init()方法执行的时候)创建,

继承了AbstractProcessor的注解处理器代码可以直接访问它。它代表了注解处理器框架提供的一个上下文环境,要创建新的代码、向编译器输出信息、获取其他工具类等都需要用到这个实例变量。

public abstract class AbstractProcessor implements Processor {
/**
* Processing environment providing by the tool framework.
*/
protected ProcessingEnvironment processingEnv;
private boolean initialized = false; public abstract boolean process(Set<? extends TypeElement> annotations,
RoundEnvironment roundEnv); }

语法糖

几乎所有的编程语言都或多或少提供过一些语法糖来方便程序员的代码开发,这些语法糖虽然不会提供实质性的功能改进,

但是它们或能提高效率,或能提升语法的严谨性,或能减少编码出错的机会。现在也有一种观点认为语法糖并不一定都是有益的,

大量添加和使用含糖的语法,容易让程序员产生依赖,无法看清语法糖的糖衣背后,程序代码的真实面目。

泛型

泛型的本质是参数化类型(Parameterized Type)或者参数化多态(Parametric Polymorphism)的应用,即可以将操作的数据类型指定为方法签名中的一种特殊参数,

这种参数类型能够用在类、接口和方法的创建中,分别构成泛型类、泛型接口和泛型方法。泛型让程序员能够针对泛化的数据类型编写相同的算法,增强了编程语言的类型系统及抽象能力。

Java选择的泛型实现方式叫作“类型擦除式泛型”(Type Erasure Generics),List< Integer > 与 List< String >仅在程序源码中是不同的,编译之后泛型都被替换成了裸类型

并在相应地方插入强制转型代码。因此在运行期,List < Integer >与List< String >是同一个类型。裸类型实现是简单粗暴地直接在编译时把ArrayList< Integer >还原回ArrayList,

只在元素访问、修改时自动插入一些强制类型转换和检查指令。如下例所示:

public static void main(String[] args) {
Map<String, String> map = new HashMap<String, String>();
map.put("hello", "Hai");
map.put("how are you?", "I'm fine.");
System.out.println(map.get("hello"));
System.out.println(map.get("how are you?"));
} // 泛型擦除后
public static void main(String[] args) {
Map map = new HashMap();
map.put("hello", "Hai");
map.put("how are you?", "I'm fine.?");
System.out.println((String) map.get("hello"));
System.out.println((String) map.get("how are you?"));
}

自动拆装箱、循环遍历

    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);
}

Javac 编译器的更多相关文章

  1. java-关于java_home配置,classpath配置和javac,java命令,javac编译器,和java虚拟机之间的关系

    在每个人学习java的第一步,都是安装jdk ,jre,配置java_home,classpath,path. 为什么要做这些?在阅读java-core的时候,看到了原理,p141. 一 关于类的共享 ...

  2. 早期(编译器)优化--javac编译器

    java语言的“编译期”其实是一段“不确定”的操作过程,可能是指一个前端编译器把.java变成.class的过程,也可能是指虚拟机的后端运行期编译器(JLT)把字节码转变成机器码的过程,也有可能是使用 ...

  3. 第一章-Javac编译器介绍

    1.Javac概述 编译器可以将编程语言的代码转换为其他形式,如Javac,将Java语言转换为虚拟机能够识别的.class文件形式.而这种将java源代码(以.java做为文件存储格式)转换为cla ...

  4. 笔记:Javac编译器

    Javac编译器是把 *.java 文件转换为 *.class 文件,是一个前端编译器:对应着有一种把字节码转变为机器码的编译器,称为JIT编译器(Just In Time Compiler),比如 ...

  5. Javac编译器详解

    学习<深入了解Java虚拟机>有一段时间了,大概理解了Java从源代码编译到执行出结果的过程,也能明确的知道Java是半解释性语言.在执行源代码时,先通过Javac编译器对源代码进行词法分 ...

  6. 早期javac编译器优化

    学习<深入了解Java虚拟机>有一段时间了,大概理解了Java从源代码编译到执行出结果的过程,也能明确的知道Java是半解释性语言.在执行源代码时,先通过Javac编译器对源代码进行词法分 ...

  7. 关于Javac编译器的那点事(一)

    Javac是什么? 它是一种编译器,将Java对人非常友好的语言,编译转化对所有机器都非常友好的语言,即:JVM能够识别的语言,也就是Java字节码.而Java字节码,说白了就是一连串二进制数字. J ...

  8. 对openjdk的javac编译器扩展了一个语法糖

    我的扩展功能描述如下: 在java的现有语法中加入var来声明变量,并且可以根据初始化数据来自动类型推导. 举两个例子: 例一: 如下JAVA代码(注意这里的var是新语法): import java ...

  9. JVM系列五(javac 编译器).

    一.概述 我们都知道 *.java 文件要首先被编译成 *.class 文件才能被 JVM 认识,这部分的工作主要由 Javac 来完成,类似于 Javac 这样的我们称之为前端编译器: 但是 *.c ...

随机推荐

  1. ES6学习笔记之 this 详解

    1.非箭头函数下的 this var obj = { x: 0, f1: function () { console.log(this.x); } } var f1 = obj.f1; var x = ...

  2. 用户RFM模型及应用

    RMF含义 R(Recency)(用户粘性,越小越好):用户最近一次交易时间的间隔.R值越大,表示用户交易发生的日期越久,反之则表示用户交易发生的日期越近 F(Frequency)(用户忠诚度,越大越 ...

  3. Windows10上基于Visual Studio Code安装Golang开发环境

    GoLang简介 Go编程语言是一个开源项目,它使程序员更具生产力. Go语言具有很强的表达能力,它简洁.清晰而高效.得益于其并发机制,用它编写的程序能够非常有效地利用多核与联网的计算机,其新颖的类型 ...

  4. Qt之先用了再说系列-串口通讯(单串口单线程)

    QT 串口通讯(单串口单线程) 串口通讯在我们写程序的时候或多或少会用到,借此在这记录一下QT是如何使用串口来通讯的.本次先侃侃在单线程下使用1个串口来通讯过程.好了,废话不多说,直接看步骤,我们的宗 ...

  5. 【Linux】通过shell脚本对mysql的增删改查以及my.cnf的配置

    目录 shell操作mysql 1.获取mysql默认密码 2.修改my.cnf文件 3.shell创建mysql数据库 4.shell创建mysql表 5.shell添加数据 6.shell删除数据 ...

  6. 其他:Windows10安装自带的Linux

    1.首先我们要打开Windows功能 2.在这里把勾打上 3.然后打开 设置 --- 更新和安全 --- 针对开发人员 --- (选择)开发人员模式 --- 确定启动 就行了 4.打开PowerShe ...

  7. buu crackRTF

    一.无壳,拖入ida,静态编译一下 整体逻辑还是很清晰,这里我的盲区是那个加密函数,是md5,没看出来,内存地址看错了,之前用黑盒动调一下,发现猜不出来,看某位wp发现有的老哥,直接sha1爆破的,狠 ...

  8. 『心善渊』Selenium3.0基础 — 23、Selenium元素等待

    目录 1.什么是元素等待 2.为什么要设置元素等待 3.Selenium中常用的等待方式 4.强制等待 5.隐式等待 (1)隐式等待介绍 (2)示例 6.显式等待 (1)显式等待介绍 (2)语法 (3 ...

  9. docker起不来报错:Failed to start Docker Application Container Engine.

    报错信息如下: [root@localhost localdisk]# systemctl restart docker Job for docker.service failed because t ...

  10. Ionic命令笔记

    Ionic命令:ionic serve 开启服务调试ionic cordova prepare android 生成android原生项目 ionic cordova run browser 打包成混 ...