大佬们好!我是LKJ_Coding,一枚初级马牛,正在努力在代码的丛林中找寻自己的方向。如果你也曾在调试中迷失,或是在文档中翻滚,那我们一定有许多共同话题可以聊!今天,我带着满满的代码“干货”来和大家分享,学不学无所谓,反正我先吐槽了!
前言
“写完 Java 代码,点一下 Run 就 OK 了嘛!” 哥,你有没有认真想过——你那一行行 Java 代码,是怎么一步步变成字节码、怎么走过 javac 的流程、编译器在背后为你擦了多少屁股?
别以为编译器只是“翻译器”,它可是整个 Java 工程体系的底座。不会玩 javac,你写再多 Lambda、再多注解,其实都没用对地方。
一、Java 编译流程:源码到字节码的七十二变
Java 编译器(javac)干了啥?不就是把 .java 文件变成 .class 吗?
这么想你就错了,编译过程其实是复杂多阶段的 pipeline:
Java 源码的编译主要分为几步:
-
词法分析(Lexical Analysis)
- 把源码字符串拆成 Token,比如标识符、关键字、分号等
-
语法分析(Syntax Analysis)
- 检查代码结构是否符合 Java 语法规则,构建 AST(抽象语法树)
-
语义分析(Semantic Analysis)
- 变量类型检查、方法重载匹配、泛型参数推断、名字解析
-
注解处理(Annotation Processing)
- 运行编译期注解处理器(APT),比如 @AutoService、@Entity、Lombok、Dagger2
-
生成字节码(Bytecode Generation)
- 把 AST 转成 JVM 指令,写成 .class 文件
-
后处理(可选,plugins)
- 一些 build 工具会加字节码增强、代码检查等
你写的每一个注解、每一个泛型、每一个 lambda,都会在这个流程中被一步步“消化”掉。
图示理解:
.java 源码
|
|--- 词法/语法分析
|
|--- 语义分析
|
|--- 注解处理(APT)
|
|--- 字节码生成
|
.class 字节码
二、javac 命令详解:常用参数你会几个?
平时谁还手动用 javac?——你要是真的懂工程、懂性能、懂注解、懂源码分析,javac 必须用得 6。
最常用的 javac 参数清单
参数 | 作用 | 常用场景 |
---|---|---|
-d |
指定输出目录 | -d out/ |
-classpath 或 -cp |
指定依赖 jar/class 路径 | -cp lib/* |
-sourcepath |
指定源码路径 | 多模块工程 |
-encoding |
指定源码文件编码 | -encoding UTF-8 |
-source |
指定源码版本 | -source 11 |
-target |
指定生成字节码版本 | -target 8 |
-parameters |
保留方法参数名 | 反射/框架必备 |
-processor |
指定注解处理器 | APT/代码生成 |
-proc:none |
禁用注解处理 | 某些特殊场景 |
-Xlint:all |
启用所有警告 | 代码检查 |
-Werror |
警告当错误 | 严格代码规范 |
典型编译命令实例
最基础
javac Hello.java
指定输出目录+依赖
javac -d out -cp lib/* src/com/myapp/*.java
编译多版本兼容代码
javac -source 1.8 -target 1.8 -encoding UTF-8 MyApp.java
开启参数名保留(Spring/反射框架开发必开)
javac -parameters -d out MyController.java
三、编译期注解处理:你以为 @Override 只是提示用的?
你用过 @Override
、@Deprecated
、@SuppressWarnings
,但你未必知道**真正的大杀器其实是编译期注解处理器(Annotation Processor)**!
1. 什么是编译期注解处理器(APT)?
- 可以在编译时扫描源代码中的注解,自动生成代码、校验逻辑、资源文件等
- 典型场景:Lombok 自动生成 getter/setter,Dagger 自动生成依赖注入代码,AutoValue/AutoService 代码生成
2. 如何写一个自定义注解处理器?
第一步:定义注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
public @interface MyAnnotation {}
第二步:实现 AbstractProcessor
@SupportedAnnotationTypes("com.example.MyAnnotation")
@SupportedSourceVersion(SourceVersion.RELEASE_11)
public class MyProcessor extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment env) {
for (Element elem : env.getElementsAnnotatedWith(MyAnnotation.class)) {
// 生成代码、检查逻辑等
}
return true;
}
}
第三步:注册处理器
在 META-INF/services/javax.annotation.processing.Processor
文件中填写:
com.example.MyProcessor
3. 用 javac 手动指定注解处理器
javac -processor com.example.MyProcessor -cp my-processor.jar -d out MyClass.java
你还可以用 -Akey=value
给注解处理器传递自定义参数。
4. APT 典型应用场景
- Lombok:无感 getter/setter/toString
- MapStruct:自动生成对象映射代码
- Dagger/Hilt:依赖注入的代码生成
- MyBatis Generator:Mapper 自动生成
四、javac 调优与源码分析 Tips
误区 / 坑点 | 建议 |
---|---|
用 IDE 就不用管 javac? | 复杂项目/自动化构建/jar 插件开发时手动 javac 更灵活 |
-target 高于 -source? | 不建议,保证 source/target 版本一致 |
依赖路径写错 | 必须绝对/相对路径,jar 用 * 通配 |
注解处理慢 | 适当用 -proc:none 禁用不必要的处理器 |
参数名丢失 | Spring/反射要加 -parameters |
五、结语:Javac 不是“点一下”,而是工程世界的幕后英雄
每个 Java 工程师都应该有一天静下心来——打开命令行,手动写一遍 javac 编译命令,看看自己写的注解如何被扫描、自己写的 Processor 如何生成代码。
你会发现:编译器才是你代码“能跑起来”的真正引擎,而 javac 的每一个参数、每一次注解扫描、每一次字节码生成,都直接影响着你项目的生产力与扩展力。
会写代码是入门,会用好编译器才是真正迈进工程的大门。
好啦,废话不多说,今天的分享就到这里!如果你觉得我这“初级马牛”还挺有趣,那就请给我点个赞、留个言、再三连击三连哦!让我们一起“成精”吧!下次见,不见不散!