You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

能否通过Java 11特性直接在代码中执行未编译的.java文件?

在Java 11中直接加载并运行未编译的.java文件(无外部依赖)

核心方案:内存编译+自定义类加载器

你期望的极简API确实不存在,但可以基于Java 11内置的javax.toolsAPI(属于java.compiler模块)封装一个工具类,实现不生成物理class文件直接加载.java源码的功能。核心思路是:

  • 使用JavaCompiler进行内存编译,将字节码输出到内存而非磁盘
  • 通过自定义类加载器加载内存中的字节码,避免文件IO操作

以下是可直接使用的封装代码:

import javax.tools.*;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.List;

public class InMemoryJavaCompiler {

    // 加载指定.java文件并返回Class对象
    public static Class<?> compileAndLoad(String javaFilePath) throws Exception {
        File sourceFile = new File(javaFilePath);
        String className = extractClassName(sourceFile);
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();

        // 自定义文件管理器,将编译后的字节码写入内存
        MemoryJavaFileManager fileManager = new MemoryJavaFileManager(compiler.getStandardFileManager(null, null, StandardCharsets.UTF_8));

        // 准备编译任务
        List<JavaFileObject> sourceFiles = new ArrayList<>();
        sourceFiles.add(new SimpleJavaFileObject(URI.create("file://" + sourceFile.getAbsolutePath()), JavaFileObject.Kind.SOURCE) {
            @Override
            public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
                return Files.readString(sourceFile.toPath());
            }
        });

        // 执行编译
        boolean success = compiler.getTask(null, fileManager, null, null, null, sourceFiles).call();
        if (!success) {
            throw new RuntimeException("编译失败:" + javaFilePath);
        }

        // 从内存加载类
        byte[] classBytes = fileManager.getClassBytes(className);
        return new ByteArrayClassLoader().defineClass(className, classBytes);
    }

    // 从.java文件中提取类名(简单实现,仅处理顶级类)
    private static String extractClassName(File sourceFile) throws IOException {
        String content = Files.readString(sourceFile.toPath());
        int classIdx = content.indexOf("class ");
        if (classIdx == -1) {
            throw new RuntimeException("未找到顶级类定义:" + sourceFile.getName());
        }
        int start = classIdx + 6;
        int end = content.indexOf(' ', start);
        if (end == -1) {
            end = content.indexOf('{', start);
        }
        return content.substring(start, end).trim();
    }

    // 自定义文件管理器,用于存储编译后的字节码到内存
    static class MemoryJavaFileManager extends ForwardingJavaFileManager<JavaFileManager> {
        private final ByteArrayJavaClassObject classObject;

        protected MemoryJavaFileManager(JavaFileManager fileManager) {
            super(fileManager);
            this.classObject = new ByteArrayJavaClassObject();
        }

        @Override
        public JavaFileObject getJavaFileForOutput(Location location, String className, JavaFileObject.Kind kind, FileObject sibling) throws IOException {
            return classObject;
        }

        public byte[] getClassBytes(String className) {
            return classObject.getBytes();
        }
    }

    // 存储编译后的字节码的JavaFileObject实现
    static class ByteArrayJavaClassObject extends SimpleJavaFileObject {
        private final ByteArrayOutputStream outputStream;

        protected ByteArrayJavaClassObject() {
            super(URI.create("byte:///" + "Dummy.class"), JavaFileObject.Kind.CLASS);
            this.outputStream = new ByteArrayOutputStream();
        }

        @Override
        public ByteArrayOutputStream openOutputStream() throws IOException {
            return outputStream;
        }

        public byte[] getBytes() {
            return outputStream.toByteArray();
        }
    }

    // 自定义类加载器,加载内存中的字节码
    static class ByteArrayClassLoader extends ClassLoader {
        public Class<?> defineClass(String name, byte[] bytes) {
            return defineClass(name, bytes, 0, bytes.length);
        }
    }
}

使用方式非常接近你期望的API:

// 加载并获取Class对象
Class<?> myClass = InMemoryJavaCompiler.compileAndLoad("MyClass.java");
// 创建实例并调用方法
Object instance = myClass.getDeclaredConstructor().newInstance();
myClass.getMethod("myMethod").invoke(instance);

关于javax.script.ScriptEngine的说明

Java标准库确实没有内置支持Java作为脚本语言的ScriptEngine实现。如果需要适配ScriptEngine规范,可以基于上面的内存编译逻辑封装一个自定义ScriptEngine:

  • 实现ScriptEngine和Compilable接口
  • 将脚本内容(Java源码)通过内存编译后加载,再反射调用方法

不过这种场景下,直接使用上面的工具类会更轻量,无需适配脚本引擎的额外规范。

关键注意事项

  • 确保你的项目依赖java.compiler模块(Java 11中默认包含,但模块化项目需要显式声明requires java.compiler;)
  • 上述代码仅处理顶级类,若需要支持内部类、包声明等复杂场景,需要完善类名提取逻辑
  • 编译时可以添加额外参数(如-classpath),通过compiler.getTask()的第三个参数传递编译选项

内容的提问来源于stack exchange,提问作者Bill K

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.17 02:25:18