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 的核心能力:

  1. 解析 Java 源码为 AST
  2. 分析代码结构
  3. 输出格式化代码
  4. 动态修改代码

下次需要处理 Java 源码时,不妨试试这个简单粗暴的工具!


原始标题:Introduction to JavaParser | Baeldung