关闭

从Java文件到字节码文件

发表于:2015-8-25 09:55

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:whthomas    来源:51Testing软件测试网采编

  本文涉及的javac编译器来自openjdk.
  javac的目录地址为:解压目录/langtools/src/share/classes/com/sun/tools/javac/
  javac编译器将Java编译成为一个有效的字节码文件会经历4个步骤:
  · 词法解析:将Java关键字排序,使得程序能有序运行。
  · 语法解析:词法解析后的Token序列整合为一颗抽象的语法树。
  · 语义解析:将抽象语法树扩展地更加完善。
  · 字节码解析:将字节码解析成完整的类。
  词法解析
  词法解析是编译器执行的字节码编译的第一步。这个步骤中,将Java源码中关键字和标识符等转换成符合规范的Token序列。
  词法解析器的接口是com.sun.tools.javac.parser.Lexer ,它直接派生于同包下面的Scanner类,它的主要任务是按照单个字符的方式读取Java源文件中的关键字和标识符等,然后将其转换为符合Java规范的Token序列。而负责词法解析工作的是com.sun.tools.javac.parser.JavacParser类,该类的对象实例由ParseFactory负责创建,JavacParser负责词法解析的具体细节。
  当我们在命令行敲入javac的时候,Java首先会调用com.sun.tools.javac.main.Main类的compile()方法。compile()方法接着就会调用JavaCompiler类的parseFile()方法,parseFile()的主要功能就是调用自己的parse()方法获得JavacParser实例对象,然后调用JavacParser类的parseCompilationUnit()进行词法解析。
  这个过程如下图所示:
  Token序列
  Token其实就是一个枚举类型,其内部定了许多符合Java语法规范并与源码字符集相对应的枚举常量。
  所有的枚举常量都在 com.sun.tools.javac.parser.Token类中。
  编译器在执行词法解析的过程中,只会对Token进行匹配校验。
  源码字符集是如何转换成Token的:
  Name对象和Token对象建立的是一种一对一的关系。当词法解析器中需要将一个源码字符集合解析成一个Token时,它会通过Names类调用Name类的fromChars()方法获得一个Name对象,然后使用Keyswords类的key(Name name)方法获得传入相对应的Token对象。
  词法解析器如何保存源码字符集和Token之间的对应关系:
  词法解析器在将源码转字符集合转换为Token之前,会先将每一个字符集合都转换成一个对应的Name对象。接着再由com.sun.tools.javac.parser.Keywords类负责实际的Token转换任务(将Token常量全部转换为Name对象),然后转换好的这些Name对象全部存到Name类的内部类Table中,Keywords类中的数组key用于保存源码字符集合和Token之间的对应关系。
  以上两个问题略微有些复杂。画了一个图来表示一下:
  调用nextToken()计算Token的获取顺序
  Keywords类的key()方法仅仅是根据Name对象获得对应的Token,而词法解析器是通过Scanner类的nextToken()方法保证Token的读取顺序规则。
  调用parseCompilationUnit()方法执行词法解析
  词法解析的核心是校验Token是否匹配com.sun.tools.javac.parser.JavacParser类在parseCompilationUnit()方法定义的匹配规则。parseCompilationUnit()方法会按照Token的匹配顺序依次解析出package、import等关键字,当这些Token匹配之后,词法解析器会开始解析class主题信息,直到词法解析全部结束,parseCompilationUnit()方法会将Token转换为一棵结构化的抽象语法树。
  语法解析
  之前提过,语法解析的目的就是将经过词法解析得到的Token整合为一棵结构化的抽象语法树。
  词法解析完成的Token序列依旧还不完善,它们还没有被整合起来,语法解析的主要任务是把这些零散的Token按照指定的Java语法规范整合起来形成一个有机的整体。
  在语法解析阶段,语法树上每个节点都直接或者间接地继承了JCTree类。
  调用qualident()方法解析package语法节点
  parseCompilationUnit()这个方法实际上,跨越了词法解析和语法解析两个阶段。当词法解析器成功将package关键字声明转换为Token并完成词法解析之后,会调用qualident()方法根据Token.PACKAGE解析为package语法节点。
  语法解析步骤中,com.sun.tools.javac.tree.TreeMaker负责创建JCTree类的所有语法节点对象实例。所以TreeMaker本身就是一个语法解析器,不过具体的细节由parseCompilationUnit()方法来控制。
  语法解析器实质上还是使用Token对应的Name对象,来作为转换语法节点的素材。所以在解析语法树之前,首先需要将Token转换成对应的Name对象。语法解析器就可以根据Name对象解析出一个JCIdent语法节点。
  当一个package关键字声明中定义了多级目录时,qualident()方法就会循环迭代调用语法解析器将package关键字声明解析为嵌套的JCFieldAccess语法节点。
21/212>
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号