Javac编译过程
javac 的作用
javac 编译器负责将包含 Java 源代码的 .java 文件转换成平台无关的字节码文件。这些字节码文件可以被任何安装了 Java 虚拟机(JVM)的设备执行,实现了 Java “一次编写,到处运行” (Write Once, Run Anywhere - WORA) 的特性。
javac 编译过程的阶段
javac 的编译过程通常可以分解为以下几个主要阶段:
-
词法分析 (Lexical Analysis / Scanning)
- 作用: 这是编译器的第一步。它将源代码字符流分解成一系列有意义的“词法单元” (Tokens)。
- 原理: 编译器会读取
.java文件中的字符,并根据 Java 语言的词法规则(如关键字、标识符、运算符、分隔符、字面量等)将其识别为不同的 Token。例如,int age = 10;会被分解为int(关键字),age(标识符),=(运算符),10(字面量),;(分隔符) 等 Token。 - 输出: 一个 Token 序列。
-
语法分析 (Syntactic Analysis / Parsing)
- 作用: 在词法分析的基础上,语法分析器会根据 Java 语言的语法规则,将 Token 序列组织成一个树状结构,称为“抽象语法树” (Abstract Syntax Tree - AST)。
- 原理: AST 是源代码的抽象表示,它移除了源代码中一些不必要的细节(如括号、分号等),但保留了代码的结构和语义信息。例如,
int age = 10;在 AST 中可能会表示为一个变量声明节点,包含变量类型、名称和初始化表达式。 - 输出: 一个抽象语法树 (AST)。如果语法不符合 Java 规范,此阶段会报告语法错误。
-
语义分析 (Semantic Analysis)
- 作用: 检查 AST,确保代码符合 Java 语言的语义规则。这包括类型检查、变量作用域检查、访问权限检查、方法签名匹配等。
- 原理:
- 类型检查: 确保操作数类型匹配,例如不能将字符串直接赋值给整型变量(除非有隐式或显式转换)。
- 作用域检查: 确保变量在使用前已声明,并且在其有效作用域内。
- 控制流分析: 检查是否有不可达代码,或者方法是否缺少返回值。
- 错误报告: 如果发现语义错误(例如尝试调用一个不存在的方法,或者类型不匹配),
javac会报告相应的错误。
- 输出: 一个经过语义检查并可能被修饰过的 AST。
-
注解处理 (Annotation Processing)
- 作用: 如果源代码中使用了自定义注解,并且存在相应的注解处理器(Annotation Processor),
javac会在此阶段调用这些处理器。 - 原理: 注解处理器可以读取、修改或生成新的 Java 源代码文件。例如,一些 Lombok 注解会在编译时生成 getter/setter 方法。这个过程可能会触发新一轮的编译循环,如果生成了新的
.java文件,javac会从头开始处理这些新文件。 - 输出: 可能生成新的
.java文件,然后再次进行词法、语法、语义分析。
- 作用: 如果源代码中使用了自定义注解,并且存在相应的注解处理器(Annotation Processor),
-
字节码生成 (Bytecode Generation)
- 作用: 将经过语义分析的 AST 转换为 JVM 能够理解和执行的字节码指令序列。
- 原理:
javac会遍历 AST,为每个节点生成对应的 JVM 字节码指令。例如,算术表达式会被转换为iadd、isub等指令;方法调用会被转换为invokevirtual、invokestatic等指令。 - 优化: 在字节码生成之前或期间,
javac可能会进行一些简单的局部优化,例如常量折叠(int x = 1 + 2;会直接编译为int x = 3;),但不涉及复杂的全局优化。 - 输出:
.class文件的二进制内容,包含字节码指令、符号表、常量池、元数据等信息。
-
写入
.class文件- 作用: 将生成的字节码写入到磁盘上的
.class文件中。 - 原理: 每个公共类(或非私有顶级类)通常对应一个独立的
.class文件,文件名为类名加上.class后缀。
- 作用: 将生成的字节码写入到磁盘上的