Javac编译原理
Java语法有规范,Java虚拟机也有自己的规范,Java语言规范和Java虚拟机规范不是一回事,它们都有自己的词法和语法解析规则,而且它们的解析规则也是不同的。如何才能让Java的语法规则适应Java虚拟机的语法规则?这个任务就由Javac编译器完成。它的任务就是将Java语言规范转化为Java虚拟机规范,完成“翻译”工作
Javac是什么
Javac是一种编译器,能将一种语言规范转换为另一种语言规范。通常将便于人理解的语言规范转化为机器容易理解的语言规范,如C,C++或者汇编语言都是直接将源码编译成目标机器码,这个目标机器码是底层CPU直接执行的指令集。而Java的Javac编译器是将源码编译成class字节码,这种编码集是JVM识别的,而JVM在运行时再将字节码转成底层机器识别的机器码
为什么需要中间添加class字节码字节码?是为了消除不同种类,不同平台机器之间的差别,实现跨平台
将.java
文件转为.class
文件,事实上是将Java的源代码转化为一连串二进制数字,这些二进制数字是有格式的,只有JVM可以识别
Javac编译器的基本结构
首先是编译程序需要的步骤:
- 读取源文件,一个字节一个字节读进来,找出这些字节有哪些是我们定于的语法关键词,这个步骤就是词法分析
- 词法分析的结果就是从源代码中找出一些规范化的Token流,接着对Token流进行语法分析,就是检查这些关键词组合在一起是不是符合Java语言规范,语法分析的结果就是形成一个符合Java语言的抽象语法树,它的作用就是就是把语言的主要词法用一个结构化的形式组合在一起
- 接下来是语义分析,虽然语法分析过不存在语法问题了,但是语义是否正确?语义分析就是把一些南通的,复杂的语法转换为更加简单的语法,对应在Java中,如foreach转为for循环,还有处理注解或其他的语法糖,最后形成一个处理过后的抽象语法树
- 最后,通过字节码生成器生成字节码,将处理过后的抽象语法树生成字节码,也就是将一种数据结构转化为另一种数据结构
所以Javac有4个模块,分别是词法分析器,语法分析器,语义分析器和字节码生成器
Javac工作原理分析
词法分析器
Javac的主要词法分析器的接口类是com.sun.tools.javac.parser.Lexer
,它的默认实现类是xom.sun,tools.javac.parser.Scanner
,Scanner会逐个读取Java源文件的首个字符,然后解析出符合Java语言规范的Token序列
通过两个Factory生成了两个接口的实现类Scanner
和JavacParser
,这个两个类负责整个词法分析的过程控制、JavacParser规定了哪些词是符合Java语法规范规定的,而具体的读取和归类不同词法的操作由Scanner完成
Token类规定了所有Java语法的合法关键字
Names用来存储和表示解析后的词法
词法分析器的分析结果就是将这个类中的关键词匹配到Token类中的任意一项,例如:Token.PACKAGE
, Token.IDENTIFIER
, Token.PUBLIC
, Token.CLASS
, Token.RBRACE(结束符)
等等
Token流中,除了在Java语法规范中定义的保留关键词,还有一个特殊的Token.IDENTIFIER
,这个Token用于表示用户定义的名称,如类名,包名,变量名,方法名等
Token流的顺序需要符合Java语言规划,如每个变量标识符之间必须用"."分隔,结束时必须跟一个";"分号。package语法,import语法,类定义,field定义,method定义,变量定义,表达式定义等,主要也就是这些语法规则,而这些语法规则除了一些Java语法规定的关键词还有就是用户自定义的变量名称了,自定义变量名称包括包名,类名,变量名,方法名,关键词和自定义名称之间用空格分隔,每个语法表达式用分号结束
语法分析器
语法分析器将Token流组建成更加结构化的语法树,也就是将一个个单词组成一句话,一个完整的语句
每个语法树上的节点都是com.sun.yoold.javac.tree.JCTree
的一个实例,关于语法树有着以下规则
- 每个语法节点都会实现一个接口
xxxTree
,这个接口又继承com.sun.source.tree.Tree
接口,如IfTree
语法节点表示一个if类型的表达式 - 每个语法节点都是
com.sun.tools.javac.JCTree
的子类,并且会实现xxxTree
接口。这个类的名称类似于JCxxx
,所有的JCxxx
类都作为一个静态内部类定义在JCTree
中
开始以Package节点进行解析,该节点解析完后进入while循环,解析importDeclaration
:首先检查Token是不是Token.IMPORT
,如果是用import、语法规则来解析import节点,最后构建出一个import语法树,最后将这个解析后的语法节点作为放入JCImport
类中
Import节点解析完成之后就是类的解析,类包括interface,class,enum
类解析后对整个classBody解析,解析结果保存在list集合中,最后将这个直接点添加在JCClassDecl
这个树中
语义分析器
还必须对语法树进行进一步分析处理,如给类添加上默认的构造函数,检查变量在使用前是否已经初始化,将一些字符串常量合并处理,检查操作变量类型是否匹配,检查所有的操作语句是否可达,检查check exception异常是否已经捕获或抛出,解除Java语法糖等等
com.suun.tools.javac.comp.Flow
类完成数据流分析,数据流分析主要完成以下工作:
- 检查变量在使用前是否已经完成正确赋值(成员变量默认值,局部变量初始值)
- 保证final修饰的变量不能重复赋值,final修饰的变量只能赋值一次,重复赋值会在编译时报错,如果这个变量是静态变量,则在定义时就必须对其赋值
- 要确定方法的返回值类型,并检查接受这个方法返回值的引用类型是否匹配,如果没有返回值则不能有任何引用类型指向这个方法的这个返回值
- 所有的check exception都要捕获或者向上抛出
- 所有的语句又要有机会被执行到,不能出现不可达的语句
在这个步骤中同时需要完成语义转换,总结如下
- 去掉无用的代码,如永假的if代码块
- 变量的自动转换,如自动拆装箱
- 去除语法糖,如foreach解析成标准的for循环形式(对数组的foreach转为fori的形式,对集合的foreach转为迭代器的形式)
- 对内部类处理内部类被重命名"外部类名$内部类名",内部类中创建一个以外部类为参数的构造函数,并会只有一个外部类对象的引用(正是存在内部类中持有外部类对象引用的构造方法,所以内部类可以直接使用外部类的属性和方法),原来内部类的位置被"{}"空的代码块替换
字节码生成器
Javac通过调用com.sun.tools.javac.jvm.Gen
类遍历语法树,生成最终的Java字节码,按照JVM的文件组织格式将字节码输出到以class为拓展名的文件中