早期 Java(JDK 5 及之前)若需在程式中呼叫編譯器,只能依賴 tools.jar 中的 com.sun.tools.javac 非標準 API,且必須手動將該 JAR 加入 Classpath。Java SE 6 引入標準化的 javax.tools 套件,提供 JavaCompiler、StandardJavaFileManager 等介面,無需額外設定即可使用。
ToolProvider.getSystemJavaCompiler() 取得 JavaCompiler 實例。run(InputStream in, OutputStream out, OutputStream err, String... arguments) 執行編譯。null 則預設使用 System.in/out/err。arguments 傳入一或多個 .java 檔案路徑。0 代表編譯成功。import java.io.*;
import javax.tools.*;
public class TestCompilerAPI {
public static void main(String[] args) throws IOException {
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
int result = compiler.run(null, null, null, "Test.java");
System.out.println(result == 0 ? "編譯成功" : "編譯失敗");
// 編譯成功後執行
if (result == 0) {
Runtime rt = Runtime.getRuntime();
Process p = rt.exec("java Test");
BufferedReader br = new BufferedReader(
new InputStreamReader(p.getInputStream()));
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
}
}
}
// Test.java
public class Test {
public static void main(String[] args) {
System.out.println("JavaCompiler 測試成功!");
}
}
編譯成功
JavaCompiler 測試成功!
若原始碼有錯誤(如 System.out.printlnln(...)),輸出將包含詳細錯誤訊息與行號。
StandardJavaFileManager 提供更精細的檔案管理、診斷資訊收集(DiagnosticListener / DiagnosticCollector)與編譯選項設定。
DiagnosticCollector<JavaFileObject> 收集編譯診斷資訊。compiler.getStandardFileManager(diagnostics, null, null) 取得 StandardJavaFileManager。fileManager.getJavaFileObjectsFromStrings(Arrays.asList("Test3.java")) 建立編譯單元集合。compiler.getTask(null, fileManager, diagnostics, options, null, compilationUnits) 取得 CompilationTask。task.call() 進行編譯,回傳 boolean 表示成敗。fileManager.close() 釋放資源。import java.io.*;
import java.util.*;
import javax.tools.*;
public class TestCompilerAPI {
private static void compileJava() throws Exception {
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<>();
StandardJavaFileManager fileManager = compiler.getStandardFileManager(
diagnostics, null, null);
Iterable<? extends JavaFileObject> compilationUnits =
fileManager.getJavaFileObjectsFromStrings(Arrays.asList("Test3.java"));
JavaCompiler.CompilationTask task = compiler.getTask(
null, fileManager, diagnostics, null, null, compilationUnits);
boolean success = task.call();
fileManager.close();
System.out.println(success ? "編譯成功" : "編譯失敗");
// 列印詳細錯誤資訊
for (Diagnostic<? extends JavaFileObject> d : diagnostics.getDiagnostics()) {
System.out.printf("Code: %s%nKind: %s%nPosition: %s%nStart: %s%nEnd: %s%nSource: %s%nMessage: %s%n%n",
d.getCode(), d.getKind(), d.getPosition(), d.getStartPosition(),
d.getEndPosition(), d.getSource(), d.getMessage(null));
}
}
public static void main(String[] args) throws Exception {
compileJava();
}
}
Iterable<String> options = Arrays.asList("-d", "D:");Arrays.asList("-sourcepath", "D:src")JavaCompiler 支援直接編譯字串形式的原始碼,無需寫入磁碟,搭配 Reflection 即時執行。
class JavaSourceFromString extends SimpleJavaFileObject {
final String code;
JavaSourceFromString(String name, String code) {
super(URI.create("string:///" + name.replace('.', '/') + Kind.SOURCE.extension), Kind.SOURCE);
this.code = code;
}
@Override
public CharSequence getCharContent(boolean ignoreEncodingErrors) {
return code;
}
}
import java.lang.reflect.*;
import java.io.*;
import javax.tools.*;
import java.util.*;
import java.net.*;
public class TestCompilerAPI {
private static void compileFromMemory() throws Exception {
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<>();
// 動態建構原始碼字串
StringWriter writer = new StringWriter();
PrintWriter out = new PrintWriter(writer);
out.println("public class HelloWorld {");
out.println(" public static void main(String[] args) {");
out.println(" System.out.println(\"Hello, World\");");
out.println(" }");
out.println("}");
out.close();
JavaFileObject file = new JavaSourceFromString("HelloWorld", writer.toString());
Iterable<? extends JavaFileObject> units = Arrays.asList(file);
JavaCompiler.CompilationTask task = compiler.getTask(
null, null, diagnostics, null, null, units);
boolean success = task.call();
System.out.println("Success: " + success);
if (success) {
System.out.println("----- 輸出 -----");
Class.forName("HelloWorld")
.getDeclaredMethod("main", new Class[]{String[].class})
.invoke(null, new Object[]{null});
System.out.println("----- 輸出結束 -----");
}
}
public static void main(String[] args) throws Exception {
compileFromMemory();
}
}
| 方法 | 適用場景 | 特點 |
|---|---|---|
JavaCompiler.run() | 簡單編譯單一/少數檔案 | 程式碼最少,直接對應命令列行為 |
StandardJavaFileManager + CompilationTask | 需控制輸出目錄、收集詳細診斷、批次編譯 | 功能最完整,適合 IDE、建構工具整合 |
| 記憶體編譯 + Reflection | 動態產生程式碼、腳本引擎、即時編譯執行 | 無磁碟 I/O,效能最佳,適合動態語言特性 |
Java SE 6 的 javax.tools 讓 Java 程式具備「編譯器即服務」的能力,是建構開發工具、動態程式碼生成框架的重要基石。