首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java SE 6 編譯器 API 完整指南:動態編譯與記憶體編譯實戰

     編輯:Java 相關知識

背景

早期 Java(JDK 5 及之前)若需在程式中呼叫編譯器,只能依賴 tools.jar 中的 com.sun.tools.javac 非標準 API,且必須手動將該 JAR 加入 Classpath。Java SE 6 引入標準化的 javax.tools 套件,提供 JavaCompiler、StandardJavaFileManager 等介面,無需額外設定即可使用。

一、使用 JavaCompiler 介面編譯原始檔

核心步驟

  1. 透過 ToolProvider.getSystemJavaCompiler() 取得 JavaCompiler 實例。
  2. 呼叫 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 進階控制編譯流程

StandardJavaFileManager 提供更精細的檔案管理、診斷資訊收集(DiagnosticListener / DiagnosticCollector)與編譯選項設定。

關鍵步驟

  1. 建立 DiagnosticCollector<JavaFileObject> 收集編譯診斷資訊。
  2. 透過 compiler.getStandardFileManager(diagnostics, null, null) 取得 StandardJavaFileManager。
  3. 使用 fileManager.getJavaFileObjectsFromStrings(Arrays.asList("Test3.java")) 建立編譯單元集合。
  4. 呼叫 compiler.getTask(null, fileManager, diagnostics, options, null, compilationUnits) 取得 CompilationTask。
  5. 執行 task.call() 進行編譯,回傳 boolean 表示成敗。
  6. 記得 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")

三、記憶體中編譯並透過 Reflection 執行

JavaCompiler 支援直接編譯字串形式的原始碼,無需寫入磁碟,搭配 Reflection 即時執行。

關鍵類別:SimpleJavaFileObject 子類別

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;
    }
}

完整範例:動態產生、編譯、執行 HelloWorld

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 程式具備「編譯器即服務」的能力,是建構開發工具、動態程式碼生成框架的重要基石。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved