Skip to content

Javac编译过程

javac 的作用

javac 编译器负责将包含 Java 源代码的 .java 文件转换成平台无关的字节码文件。这些字节码文件可以被任何安装了 Java 虚拟机(JVM)的设备执行,实现了 Java “一次编写,到处运行” (Write Once, Run Anywhere - WORA) 的特性。

javac 编译过程的阶段

javac 的编译过程通常可以分解为以下几个主要阶段:

  1. 词法分析 (Lexical Analysis / Scanning)

    • 作用: 这是编译器的第一步。它将源代码字符流分解成一系列有意义的“词法单元” (Tokens)。
    • 原理: 编译器会读取 .java 文件中的字符,并根据 Java 语言的词法规则(如关键字、标识符、运算符、分隔符、字面量等)将其识别为不同的 Token。例如,int age = 10; 会被分解为 int (关键字), age (标识符), = (运算符), 10 (字面量), ; (分隔符) 等 Token。
    • 输出: 一个 Token 序列。
  2. 语法分析 (Syntactic Analysis / Parsing)

    • 作用: 在词法分析的基础上,语法分析器会根据 Java 语言的语法规则,将 Token 序列组织成一个树状结构,称为“抽象语法树” (Abstract Syntax Tree - AST)。
    • 原理: AST 是源代码的抽象表示,它移除了源代码中一些不必要的细节(如括号、分号等),但保留了代码的结构和语义信息。例如,int age = 10; 在 AST 中可能会表示为一个变量声明节点,包含变量类型、名称和初始化表达式。
    • 输出: 一个抽象语法树 (AST)。如果语法不符合 Java 规范,此阶段会报告语法错误。
  3. 语义分析 (Semantic Analysis)

    • 作用: 检查 AST,确保代码符合 Java 语言的语义规则。这包括类型检查、变量作用域检查、访问权限检查、方法签名匹配等。
    • 原理:
      • 类型检查: 确保操作数类型匹配,例如不能将字符串直接赋值给整型变量(除非有隐式或显式转换)。
      • 作用域检查: 确保变量在使用前已声明,并且在其有效作用域内。
      • 控制流分析: 检查是否有不可达代码,或者方法是否缺少返回值。
      • 错误报告: 如果发现语义错误(例如尝试调用一个不存在的方法,或者类型不匹配),javac 会报告相应的错误。
    • 输出: 一个经过语义检查并可能被修饰过的 AST。
  4. 注解处理 (Annotation Processing)

    • 作用: 如果源代码中使用了自定义注解,并且存在相应的注解处理器(Annotation Processor),javac 会在此阶段调用这些处理器。
    • 原理: 注解处理器可以读取、修改或生成新的 Java 源代码文件。例如,一些 Lombok 注解会在编译时生成 getter/setter 方法。这个过程可能会触发新一轮的编译循环,如果生成了新的 .java 文件,javac 会从头开始处理这些新文件。
    • 输出: 可能生成新的 .java 文件,然后再次进行词法、语法、语义分析。
  5. 字节码生成 (Bytecode Generation)

    • 作用: 将经过语义分析的 AST 转换为 JVM 能够理解和执行的字节码指令序列。
    • 原理: javac 会遍历 AST,为每个节点生成对应的 JVM 字节码指令。例如,算术表达式会被转换为 iaddisub 等指令;方法调用会被转换为 invokevirtualinvokestatic 等指令。
    • 优化: 在字节码生成之前或期间,javac 可能会进行一些简单的局部优化,例如常量折叠(int x = 1 + 2; 会直接编译为 int x = 3;),但不涉及复杂的全局优化。
    • 输出: .class 文件的二进制内容,包含字节码指令、符号表、常量池、元数据等信息。
  6. 写入 .class 文件

    • 作用: 将生成的字节码写入到磁盘上的 .class 文件中。
    • 原理: 每个公共类(或非私有顶级类)通常对应一个独立的 .class 文件,文件名为类名加上 .class 后缀。