你想要获取一个关于 Java 核心 API 中 JavaCompiler 接口的完整使用示例,这个接口可以让你在 Java 程序中动态编译 Java 源代码,无需借助外部 javac 命令。

什么是 JavaCompiler

JavaCompilerjavax.tools 包下的核心接口,属于 JDK 内置工具 API,允许你:

  1. 动态编译字符串形式的 Java 源码
  2. 编译磁盘上的 .java 文件
  3. 获取编译错误/警告信息
  4. 自定义编译输出目录、类路径等

注意:该接口仅在 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!

总结

  1. JavaCompiler 核心价值是在Java程序内动态编译源码,支持内存源码和磁盘文件两种编译方式。
  2. 关键步骤:获取编译器实例 → 准备源码(内存/文件)→ 配置编译参数 → 执行编译 → 处理诊断信息。
  3. 运行时必须依赖JDK环境,编译结果可通过反射加载并执行,常用于动态代码生成、自定义编译器工具等场景。

扩展场景

  • 动态生成并编译自定义注解处理器。
  • 实现简易的在线代码运行工具(如在线Java编译器)。
  • 批量编译指定目录下的所有.java文件,并自定义编译规则。
Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐