Java编译过程_Java源代码编译阶段详解

javac默认编译流程共七个关键阶段:Parse(词法语法分析生成AST)、Enter(构建符号表)、Process(注解处理)、Attribution(类型与语义检查)、Flow(控制流分析)、Desugar(语法糖降级)、Generate(字节码生成)。

Java 源代码编译不是简单地把 .java 文件“翻译”成 .class 文件,而是一套分阶段、带语义检查的流程;直接用 javac 命令看似一步完成,背后实际包含词法分析、语法解析、符号表构建、注解处理、字节码生成等多个逻辑阶段。

javac 的默认编译流程分哪几个关键阶段

javac 启动到输出 .class,核心阶段包括:

  • Parse:读入源码,做词法和语法分析,生成抽象语法树(AST),此时只检查基础语法(如括号是否匹配、if 后是否跟括号)
  • Enter:将类、方法、字段等符号填入符号表,建立作用域关系;此阶段失败会报 cannot find symbol
  • Process:运行注解处理器(如 Lombok、MapStruct 的 @Data@Mapper),可能生成新源文件或修改 AST
  • Attribution:类型检查与语义分析,确认变量类型、方法重载、泛型擦除后是否合法;incompatible typesbad operand types 都在此阶段抛出
  • Flow:控制流分析,检查变量是否初始化、是否可达、是否有 unreachable code
  • D

    esugar
    :将高阶语法糖(如 lambda、try-with-resources、switch 表达式)降级为 JVM 支持的字节码结构
  • Generate:遍历 AST,生成符合 JVM 规范的 .class 字节码(含常量池、字段/方法表、Code 属性等)

为什么加了 -Xlint 能发现更多问题

-Xlint 不是开启新阶段,而是让 AttributionFlow 阶段启用更严格的检查规则。默认情况下,很多潜在问题(如未使用的变量、过时 API 调用、序列化 UID 缺失)被静默忽略。

  • javac -Xlint:all 会报告所有可检测的警告,比如 serial(缺少 serialVersionUID)、fallthrough(switch 中漏写 break
  • -Xlint:-unchecked 可关闭某类警告,但要注意:关闭 unchecked 不会跳过泛型类型擦除检查,只是不提示“未经检查的转换”这类信息
  • 部分 -Xlint 提示(如 dep-ann)依赖源码中是否存在 @Deprecated 注解,不是仅靠字节码推断

编译时报错 “class file for XXX not found” 是哪个阶段的问题

这是 Enter 阶段失败的典型表现——符号表构建时找不到依赖类的定义。它和运行时的 NoClassDefFoundError 容易混淆,但发生时机完全不同。

  • 常见原因:类路径(-cp-classpath)没包含该类所在的 JAR 或目录;或模块路径(--module-path)未正确配置模块依赖
  • 注意:即使该类在运行时可通过 classloader 加载,编译期也必须显式可见;javac 不会去扫描整个 lib/ 目录猜依赖
  • 若用 Maven,确保 compile scope 的依赖已正确声明;IDE 中有时缓存了旧 classpath,需刷新项目(如 IntelliJ 的 Reload project

真正影响编译结果的,不是你写了什么语法,而是符号表怎么建、类型怎么推、注解怎么展开;很多“明明能跑通却编译不过”的问题,根源都在 EnterAttribution 阶段的隐式约束上。