Javac编译原理 (简述)

转于:https://blog.csdn.net/mine_song/article/details/72910004 感谢

前几日,看书看到这一章,人懒,不想记录,所以就直接转了该博主的文章,感谢!

 

 

1、javac作用

将*.java源代码文件转化为*.class文件
 

2、编译流程

流程:

词法分析器:将源码转换为Token流
将源代码划分成一个个Token(Token包含的元素类型看3.2)
语法分析器:将Token流转化为语法树
将上述的一个个Token组成一句句话(或者说成一句句代码块),检查这一句句话是不是符合Java语言规范
语义分析器:将语法树转化为注解语法树
将复杂的语法转化成简单的语法(eg.注解、foreach转化为for循环)并做一些检查,添加一些代码
代码生成器:将注解语法树转化为字节码
 

3、词法分析

3.1、作用

将源码转换为Token流。
3.2、流程

一个字节一个字节的读取源代码,形成规范化的Token流。规范化的Token包含:

java关键词:package、import、public、class、int等
自定义单词:包名、类名、变量名、方法名
符号:=、;、+、-、*、/、%、{、}等
3.3、示例

代码:

package compile;
 /**
 * 词法
 */
public class Cifa {
    int a;
    int c = a + 1;
}


以上代码转化为的Token流:

说明:完成以上示例的是JavacParser的parseCompilationUnit()方法,源代码见文章开头的书籍。

注意:上边的token流符合java语言规范。

3.4、疑问

怎样判断package是java关键词还是自定义变量?
JavacParser会根据java语言规范来控制什么顺序、什么地方出现什么Token(这个查看parseCompilationUnit()源码就知道了),所以package在文件的最开头出现,我们会知道是一个Token.PACKAGE类型,而非自定义的Token.IDENTIFIER类型。
一条实践:在编写程序的时候,不要用java关键词来定义变量名、类名、包名、方法名,而是采取一定有意义的单词来定义,当然,你再eclipse中编写代码的时候,如果使用了java关键词来定义变量,eclipse会提醒你这是一个错误的定义。
怎样确定package是一个Token,而packa不是?
我的理解是,主要看空格和符号(符号见3.2),对于package是一个单词,中间没有空格也没有符号,所以是一个Token
一条实践:在编写代码时,例如:int a = b + c;//a与=中间有一个空格、=与b之间有一个空格、b与+之间有一个空格、+与c之间有一个空格,当然,这里没有空格也行,因为每一个变量之间正好都是由符号来隔开的,但是之前看了一个视频说,如果上边这句话没有这些空格的话,可能编译不通过,所以我们最好还是加上空格,当然加上空格后显得整个代码也清晰。
 

4、语法分析

4.1、作用

将进行词法分析后形成的Token流中的一个个Token组成一句句话,检查这一句句话是不是符合Java语言规范。
4.2、语法分析三部分:

package
import
类(包含class、interface、enum),一下提到的类泛指这三类,并不单单是指class
4.3、示例

代码:

package compile;
 
 /**
  * 语法
  */
public class Yufa {
    int a;
    private int c = a + 1;
         //getter
    public int getC() {
        return c;
   } 
   //setter
    public void setC(int c) {
         this.c = c;
     }
}


最终语法树:

说明:

每一个包package下的所有类都会放在一个JCCompilationUnit节点下,在该节点下包含:package语法树(作为pid)、各个类的语法树
每一个从JCClassDecl发出的分支都是一个完整的代码块,上述是四个分支,对应我们代码中的两行属性操作语句和两个方法块代码块,这样其实就完成了语法分析器的作用:将一个个Token单词组成了一句句话(或者说成一句句代码块)
在上述的语法树部分,对于属性操作部分是完整的,但是对于两个方法块,省略了一些语法节点,例如:方法修饰符public、方法返回类型、方法参数。
疑问:

import节点的语法树与package的相似,但是import语法树放在了哪一个地方?

 

5、语义分析

5.1、作用

将语法树转化为注解语法树
5.2、步骤

添加默认的无参构造器(在没有指定任何有参构造器的情况下)
处理注解
标注:检查语义合法性、进行逻辑判断
检查语法树中的变量类型是否匹配(eg.String s = 1 + 2;//这样"="两端的类型就不匹配)
检查变量、方法或者类的访问是否合法(eg.一个类无法访问另一个类的private方法)
变量在使用前是否已经声明、是否初始化
常量折叠(eg.代码中:String s = "hello" + "world",语义分析后String s = "helloworld")
推导泛型方法的参数类型
数据流分析
变量的确定性赋值(eg.有返回值的方法必须确定有返回值)
final变量只能赋一次值,在编译的时候再赋值的话会报错
所有的检查型异常是否抛出或捕获
所有的语句都要被执行到(return后边的语句就不会被执行到,除了finally块儿)
进一步语义分析
去掉永假代码(eg.if(false))
变量自动转换(eg.int和Integer)
去掉语法糖(eg.foreach转化为for循环,assert转化为if,内部类解析成一个与外部类相关联的外部类)
最后,将经过上述处理的语法树转化为最后的注解语法树

 

6、生成字节码

6.1、作用

将注解语法树转化成字节码,并将字节码写入*.class文件。
6.2、步骤

将java的代码块转化为符合JVM语法的命令形式,这就是字节码
按照JVM的文件组织格式将字节码输出到*.class文件中

具体的源代码与步骤查看com.sun.tools.javac.jvm.Gen类与《分布式Java应用:基础与实践》P42

6.3、class文件包含的内容

在生成的*.class文件中不只包含字节码信息,具体包含:

结构信息
class文件格式版本号
各部分的数量与大小
元数据
类、父类、实现接口的声明信息
属性声明信息
方法声明信息
常量池
方法信息
字节码
异常处理器表
局部变量区的大小
操作数栈的大小
操作数栈的类型记录
调试用符号信息
总结:

对于编译这一块儿,我们在实际操作中不会直接去操作这些代码,不像类加载器机制,我们可能需要自己编写类加载工具,也不像Java内存管理那样,我们会直接在服务器配置堆栈方法区空间、配置GC收集器等,但是了解javac编译,对于我们了解以后的类文件结构、类加载机制有一定的帮助,也有利于我们掌握整个Java代码的执行流程,对于我们了解编译期间编译器做的一些检查工作也有很大帮助,了解这些检查工作有利于我们在写代码的时候更加小心,例如,检查型异常都需要捕获或抛出,每一条语句都要被执行到(即可达)等。虽然,这些工作eclipse会在我们写代码的时候为我们自动去检查,包括检查语句是否可达,但是了解这些还是有好处的。

附:对象分配(《实战java虚拟机》)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值