1. 引言
本文将带你了解 JavaParser 库的核心功能。我们将探讨它的用途、能力以及如何高效使用它。
2. JavaParser 是什么?
JavaParser 是一个开源的 Java 源码处理库。它能将 Java 源码解析为抽象语法树(AST)。 解析完成后,我们可以分析代码结构、修改代码逻辑,甚至生成新代码。
JavaParser 支持解析 Java 18 及以下版本的源码,涵盖所有稳定语言特性,但不包含预览特性。
3. 依赖配置
使用 JavaParser 前需引入最新版本(当前为 3.25.10)。
核心依赖是 javaparser-core
。Maven 用户在 pom.xml
中添加:
<dependency>
<groupId>com.github.javaparser</groupId>
<artifactId>javaparser-core</artifactId>
<version>3.25.10</version>
</dependency>
Gradle 用户在 build.gradle
中添加:
implementation("com.github.javaparser:javaparser-core:3.25.10")
此外还有两个可选依赖:
javaparser-symbol-solver-core
:分析 AST 中元素间的引用关系javaparser-core-serialization
:实现 AST 与 JSON 的双向转换
4. 解析 Java 代码
配置好依赖后,就可以开始解析代码了。所有解析操作都从 StaticJavaParser
类开始。 根据解析目标和来源不同,提供了多种解析方法。
4.1. 解析源文件
解析完整源文件使用 StaticJavaParser.parse()
方法。 它支持多种输入形式:
- 字符串
- 本地文件(
File
) - 资源流(
InputStream
/Reader
)
示例:解析类定义生成 CompilationUnit
(AST 根节点)
CompilationUnit parsed = StaticJavaParser.parse("class TestClass {}");
4.2. 解析语句
解析单条语句使用 StaticJavaParser.parseStatement()
。 该方法仅接受字符串输入,返回 Statement
对象:
Statement parsed = StaticJavaParser.parseStatement("final int answer = 42;");
4.3. 解析其他结构
JavaParser 支持解析 Java 18 的所有语言结构,每种结构都有专用解析方法:
parseAnnotation()
→ 解析注解parseImport()
→ 解析导入语句parseBlock()
→ 解析代码块- 其他结构同理
⚠️ 关键点:必须明确要解析的代码类型,否则会失败。例如用 parseStatement()
解析类定义会抛出异常。
4.4. 处理错误代码
解析失败时 JavaParser 会抛出 ParseProblemException
,精确定位错误位置。 示例:解析不完整的类定义
ParseProblemException parseProblemException = assertThrows(ParseProblemException.class,
() -> StaticJavaParser.parse("class TestClass"));
assertEquals(1, parseProblemException.getProblems().size());
assertEquals("Parse error. Found <EOF>, expected one of \"<\" \"extends\" \"implements\" \"permits\" \"{\"",
parseProblemException.getProblems().get(0).getMessage());
错误信息明确指出:类定义后缺少泛型声明、继承/实现关键字或类体起始符 {
。
5. 分析解析后的代码
解析完成后,我们可以像使用反射一样分析 AST 结构,但操作对象是源码而非运行时类。
5.1. 访问解析元素
通过 AST 查询特定元素的方式取决于解析目标类型。 示例:从 CompilationUnit
获取指定类
Optional<ClassOrInterfaceDeclaration> cls = compilationUnit.getClassByName("TestClass");
✅ 返回 Optional
因为类可能不存在
❌ 类名等必存在属性(如 ClassOrInterfaceDeclaration.getName()
)不返回 Optional
层级访问规则:只能直接访问当前层级的直接子元素。例如:
CompilationUnit
可访问包声明、导入语句、顶层类型- 需要先获取类声明,才能访问其成员
5.2. 遍历解析元素
当需要处理同类型的所有元素时,可通过集合方法批量获取。 示例:获取所有导入语句
NodeList<ImportDeclaration> imports = compilationUnit.getImports();
✅ 返回 NodeList
(实现 List
接口)
❌ 无导入时返回空列表而非 null
5.3. 遍历整个 AST
所有 AST 类型都实现了访问者模式,支持全树遍历:
compilationUnit.accept(visitor, arg);
提供两种标准访问者:
VoidVisitor
无返回值访问者,通过 VoidVisitorAdapter
适配器实现:
compilationUnit.accept(new VoidVisitorAdapter<Object>() {
@Override
public void visit(MethodDeclaration n, Object arg) {
super.visit(n, arg);
System.out.println("Method: " + n.getName());
}
}, null);
✅ 自动遍历所有方法(包括嵌套类/匿名类中的方法)
GenericVisitor<R, A>
带返回值访问者,通过 GenericListVisitorAdaptor
收集结果:
List<String> allMethods = compilationUnit.accept(new GenericListVisitorAdapter<String, Object>() {
@Override
public List<String> visit(MethodDeclaration n, Object arg) {
List<String> result = super.visit(n, arg);
result.add(n.getName().asString());
return result;
}
}, null);
✅ 返回包含所有方法名的列表
6. 输出解析后的代码
除了解析和分析,还能将 AST 转换回源码字符串。
简单输出
直接调用 toString()
即可生成格式化代码:
// 原始代码
package com.baeldung.javaparser;
import java.util.List;
class TestClass {
private List<String> doSomething() {}
private class Inner {
private String other() {}
}
}
// 输出结果
package com.baeldung.javaparser;
import java.util.List;
class TestClass {
private List<String> doSomething() {
}
private class Inner {
private String other() {
}
}
}
✅ 自动格式化(可能与原始格式不同但符合规范)
自定义格式化
通过 DefaultPrettyPrinterVisitor
控制输出格式:
DefaultPrinterConfiguration printerConfiguration = new DefaultPrinterConfiguration();
printerConfiguration.addOption(new DefaultConfigurationOption(DefaultPrinterConfiguration.ConfigOption.INDENTATION,
new Indentation(Indentation.IndentType.SPACES, 2)));
DefaultPrettyPrinterVisitor visitor = new DefaultPrettyPrinterVisitor(printerConfiguration);
compilationUnit.accept(visitor, null);
String formatted = visitor.toString();
✅ 示例:将缩进改为 2 个空格
7. 修改解析后的代码
AST 本质是对象模型,可直接修改其结构。 结合代码输出能力,可实现:
- IDE 插件开发
- 编译时代码生成
- 自动化重构
修改方式灵活:直接访问、遍历器或混合使用均可。示例:将所有方法名转为大写
compilationUnit.accept(new VoidVisitorAdapter<Object>() {
@Override
public void visit(MethodDeclaration n, Object arg) {
super.visit(n, arg);
String oldName = n.getName().asString();
n.setName(oldName.toUpperCase());
}
}, null);
✅ 修改后 AST 立即生效
✅ 输出代码将反映所有变更
8. 总结
本文快速介绍了 JavaParser 的核心能力:
- 解析 Java 源码为 AST
- 分析代码结构
- 输出格式化代码
- 动态修改代码
下次需要处理 Java 源码时,不妨试试这个简单粗暴的工具!