豆包 一个关于 Java 核心 API 中 `JavaCompiler` 接口的完整使用示例,这个接口可以让你在 Java 程序中动态编译 Java 源代码,无需借助外部 `javac` 命令
·
你想要获取一个关于 Java 核心 API 中 JavaCompiler 接口的完整使用示例,这个接口可以让你在 Java 程序中动态编译 Java 源代码,无需借助外部 javac 命令。
什么是 JavaCompiler
JavaCompiler 是 javax.tools 包下的核心接口,属于 JDK 内置工具 API,允许你:
- 动态编译字符串形式的 Java 源码
- 编译磁盘上的
.java文件 - 获取编译错误/警告信息
- 自定义编译输出目录、类路径等
注意:该接口仅在 JDK(而非 JRE)中可用,运行时需确保使用 JDK 环境。
完整使用示例
以下 Demo 包含 编译字符串源码 和 编译磁盘文件 两种核心场景,并处理编译错误输出:
import javax.tools.*;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.List;
/**
* JavaCompiler 接口完整使用示例
* 功能:1. 编译字符串形式的Java源码 2. 编译磁盘上的.java文件
*/
public class JavaCompilerDemo {
// 编译输出目录(生成的.class文件存放位置)
private static final String OUTPUT_DIR = "compiled_classes";
public static void main(String[] args) {
try {
// 初始化输出目录
initOutputDir();
// 场景1:编译字符串形式的Java源码
compileSourceFromString();
// 场景2:先写入源码文件到磁盘,再编译该文件
compileSourceFromFile();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 初始化编译输出目录
*/
private static void initOutputDir() throws IOException {
Path outputPath = Paths.get(OUTPUT_DIR);
if (!Files.exists(outputPath)) {
Files.createDirectory(outputPath);
System.out.println("✅ 已创建编译输出目录:" + OUTPUT_DIR);
}
}
/**
* 场景1:编译字符串形式的Java源码(无需写入磁盘)
*/
private static void compileSourceFromString() {
// 1. 获取系统默认的Java编译器实例
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
if (compiler == null) {
System.err.println("❌ 未找到JavaCompiler!请确保使用JDK运行(而非JRE)");
return;
}
// 2. 创建内存中的Java源码(HelloWorld类)
String sourceCode = """
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello from dynamically compiled code!");
System.out.println("Compiled by JavaCompiler API");
}
}""";
// 3. 创建自定义的JavaFileObject(内存源码)
JavaFileObject sourceFile = new SimpleJavaFileObject(
URI.create("string:///HelloWorld.java"),
JavaFileObject.Kind.SOURCE
) {
@Override
public CharSequence getCharContent(boolean ignoreEncodingErrors) {
return sourceCode; // 返回字符串源码
}
};
// 4. 配置编译参数(指定输出目录、编码等)
List<String> compileOptions = Arrays.asList(
"-d", OUTPUT_DIR, // 指定.class文件输出目录
"-encoding", "UTF-8", // 源码编码
"-Xlint:unchecked" // 启用unchecked警告
);
// 5. 创建编译任务
DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<>(); // 收集编译诊断信息
JavaCompiler.CompilationTask task = compiler.getTask(
null, // 输出流(null使用System.err)
null, // 类文件管理器(默认)
diagnostics, // 诊断信息收集器
compileOptions, // 编译选项
null, // 要处理的注解(null表示所有)
List.of(sourceFile) // 要编译的源码文件
);
// 6. 执行编译
boolean success = task.call();
// 7. 处理编译结果
if (success) {
System.out.println("\n✅ 场景1:字符串源码编译成功!");
// 验证:加载并运行编译后的类
runCompiledClass("HelloWorld");
} else {
System.err.println("\n❌ 场景1:字符串源码编译失败!");
printDiagnostics(diagnostics); // 打印错误信息
}
}
/**
* 场景2:编译磁盘上的.java文件
*/
private static void compileSourceFromFile() throws IOException {
// 1. 获取编译器实例
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
if (compiler == null) return;
// 2. 先创建一个测试的.java文件到磁盘
String fileName = "TestFile.java";
Path sourcePath = Paths.get(fileName);
String fileSourceCode = """
public class TestFile {
public static void sayHello() {
System.out.println("Hello from file compilation!");
}
}""";
Files.write(sourcePath, fileSourceCode.getBytes(StandardCharsets.UTF_8));
System.out.println("\n✅ 已创建测试源码文件:" + fileName);
// 3. 获取文件管理器(处理磁盘文件)
StandardJavaFileManager fileManager = compiler.getStandardFileManager(
null, null, StandardCharsets.UTF_8
);
// 4. 定位要编译的.java文件
Iterable<? extends JavaFileObject> compilationUnits = fileManager.getJavaFileObjects(fileName);
// 5. 配置编译参数
List<String> compileOptions = Arrays.asList("-d", OUTPUT_DIR, "-encoding", "UTF-8");
DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<>();
// 6. 执行编译
JavaCompiler.CompilationTask task = compiler.getTask(
null, fileManager, diagnostics, compileOptions, null, compilationUnits
);
boolean success = task.call();
// 7. 关闭文件管理器
fileManager.close();
// 8. 处理结果
if (success) {
System.out.println("✅ 场景2:磁盘文件编译成功!");
runCompiledClass("TestFile");
// 清理测试文件
Files.deleteIfExists(sourcePath);
} else {
System.err.println("❌ 场景2:磁盘文件编译失败!");
printDiagnostics(diagnostics);
}
}
/**
* 运行编译后的类(通过反射)
* @param className 类名(如 HelloWorld)
*/
private static void runCompiledClass(String className) {
try {
// 将输出目录加入类路径,加载编译后的类
File outputDir = new File(OUTPUT_DIR);
URLClassLoader classLoader = new URLClassLoader(new java.net.URL[]{outputDir.toURI().toURL()});
Class<?> clazz = classLoader.loadClass(className);
// 调用main方法或指定方法
if (className.equals("HelloWorld")) {
clazz.getMethod("main", String[].class).invoke(null, (Object) new String[0]);
} else if (className.equals("TestFile")) {
clazz.getMethod("sayHello").invoke(null);
}
classLoader.close();
} catch (Exception e) {
System.err.println("❌ 运行编译后的类失败:" + e.getMessage());
}
}
/**
* 打印编译诊断信息(错误/警告)
*/
private static void printDiagnostics(DiagnosticCollector<JavaFileObject> diagnostics) {
System.err.println("📋 编译诊断信息:");
for (Diagnostic<? extends JavaFileObject> diagnostic : diagnostics.getDiagnostics()) {
String message = String.format(
" %s [行%d, 列%d]: %s",
diagnostic.getKind(), // 错误类型(ERROR/WARNING)
diagnostic.getLineNumber(),
diagnostic.getColumnNumber(),
diagnostic.getMessage(null)
);
System.err.println(message);
}
}
}
代码关键说明
1. 核心组件
ToolProvider.getSystemJavaCompiler():获取 JDK 内置的编译器实例(JRE 中返回 null)。JavaFileObject:表示 Java 源码/类文件,示例中自定义实现了内存源码的加载。DiagnosticCollector:收集编译过程中的错误、警告信息,便于调试。CompilationTask:编译任务对象,call()方法执行编译并返回布尔结果。
2. 编译参数说明
| 参数 | 作用 |
|---|---|
-d |
指定生成的.class文件输出目录 |
-encoding |
指定源码文件编码(如 UTF-8) |
-Xlint |
启用编译警告(如 unchecked) |
3. 运行前置条件
- 必须使用 JDK 运行(而非 JRE),否则
ToolProvider.getSystemJavaCompiler()返回 null。 - 运行目录需有读写权限(创建输出目录和测试文件)。
4. 输出结果示例
✅ 已创建编译输出目录:compiled_classes
✅ 场景1:字符串源码编译成功!
Hello from dynamically compiled code!
Compiled by JavaCompiler API
✅ 已创建测试源码文件:TestFile.java
✅ 场景2:磁盘文件编译成功!
Hello from file compilation!
总结
JavaCompiler核心价值是在Java程序内动态编译源码,支持内存源码和磁盘文件两种编译方式。- 关键步骤:获取编译器实例 → 准备源码(内存/文件)→ 配置编译参数 → 执行编译 → 处理诊断信息。
- 运行时必须依赖JDK环境,编译结果可通过反射加载并执行,常用于动态代码生成、自定义编译器工具等场景。
扩展场景
- 动态生成并编译自定义注解处理器。
- 实现简易的在线代码运行工具(如在线Java编译器)。
- 批量编译指定目录下的所有.java文件,并自定义编译规则。
更多推荐




所有评论(0)