• 第二章 Javac编译原理


    注:本文主要记录自《深入分析java web技术内幕》"第四章 javac编译原理"

    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、示例

    代码:

    1 package compile;
    2 
    3 /**
    4  * 词法
    5  */
    6 public class Cifa {
    7     int a;
    8     int c = a + 1;
    9 }
    View Code

    以上代码转化为的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、示例

    代码:

     1 package compile;
     2 
     3 /**
     4  * 语法
     5  */
     6 public class Yufa {
     7     int a;
     8     private int c = a + 1;
     9     
    10     //getter
    11     public int getC() {
    12         return c;
    13     }
    14     //setter
    15     public void setC(int c) {
    16         this.c = c;
    17     }
    18 }
    View Code

    最终语法树:

    说明:

    • 每一个包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文件格式版本号
      • 各部分的数量与大小
    • 元数据
      • 类、父类、实现接口的声明信息
      • 属性声明信息
      • 方法声明信息
      • 常量池
    • 方法信息
      • 字节码
      • 异常处理器表
      • 局部变量区的大小
      • 操作数栈的大小
      • 操作数栈的类型记录
      • 调试用符号信息

    这里提到的局部变量区和操作数栈组成了了方法栈,可以参看第一章 JVM内存结构

    总结:

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

  • 相关阅读:
    PAT (Advanced Level) Practice 1054 The Dominant Color (20 分)
    PAT (Advanced Level) Practice 1005 Spell It Right (20 分) (switch)
    PAT (Advanced Level) Practice 1006 Sign In and Sign Out (25 分) (排序)
    hdu 5114 Collision
    hdu4365 Palindrome graph
    单链表查找最大值、两个递增的链表合并并且去重
    蓝桥杯-最短路 (SPFA算法学习)
    蓝桥杯-最大最小公倍数
    Codeforces-470 div2 C题
    蓝桥杯-地宫取宝
  • 原文地址:https://www.cnblogs.com/java-zhao/p/5194064.html
Copyright © 2020-2023  润新知