javac 执行分阶段编译:词法→语法→语义→注解处理→字节码生成;需区分源文件路径与-classpath;.class含魔数、常量池等结构;泛型擦除、try-with-resources展开、var脱糖均在编译期完成。

javac 命令到底做了哪些事
javac 不是简单地“翻译”源码,它执行的是一个分阶段编译流程:词法分析 → 语法分析 → 语义分析 → 注解处理 → 字节码生成。每个阶段出错都会中断,且错误类型不同——比如 cannot find symbol 是语义分析失败,illegal start of expression 是语法分析报错。
实际操作中,你不需要手动触发各阶段,但理解这点能帮你快速定位问题根源。例如,如果 @Override 报错但方法签名明显正确,很可能是类路径没配对,导致语义分析时父类没被加载进来。
classpath 和源文件路径必须分开指定
很多人误以为把 .java 文件放哪、用什么路径运行 javac 都无所谓,其实不然:javac 默认只在当前目录找源文件,但依赖的 class(如第三方库或自定义类)必须通过 -cp 或 -classpath 显式声明。
- 源文件路径决定
javac扫描和编译哪些.java文件(支持通配符,如javac src/com/example/*.java) -
-cp决定编译期能“看到”哪些已编译类(影响import解析和类型检查) - 若源码中有
package com.example;,则.java必须放在com/example/子目录下,否则报class is not in directory
生成的 .class 文件结构不是黑盒
每个 .class 文件本质是 JVM 可识别的二进制格式,含魔数(CAFEBABE)、版本号、常量池、字段/方法表、字节码指令等。你可以用 javap -v 查看细节:
立即学习“Java免费学习笔记(深入)”;
javap -v MyClass.class
常见误判点:javap 输出里的 LineNumberTable 和 LocalVariableTable 默认不生成(除非加 -g 编译),这会影响调试体验;而 synthetic 标记的方法(如 lambda 生成的桥接方法)不会出现在源码里,但会真实存在于 .class 中。
泛型、try-with-resources、var 等语法糖怎么消失的
Java 编译器在生成字节码前会做“脱糖”(desugaring):所有高阶语法都会降级为 JVM 原生支持的指令。这意味着你写的代码和最终运行的字节码之间存在明确但不可逆的映射关系。
-
List编译后只剩List,类型参数被擦除,仅保留Signature属性供反射读取 -
try-with-resources被展开为带finally块的手动close()调用 -
var在局部变量声明中仅影响编译期类型推导,生成的字节码与显式写出类型完全一致
真正容易被忽略的是:这些变换发生在编译期,且不可绕过。哪怕你用 ASM 直接改字节码,也得遵守擦除后的类型约束,否则 VerifyError 会在类加载时爆发。









