【问题标题】:Load and compile an external .java file加载并编译外部 .java 文件
【发布时间】:2020-10-01 16:28:07
【问题描述】:

加载.java 文件的最佳方法是调用main 方法并执行代码。因此,例如,如果我调用 C:/Test.java 它应该执行代码。

我现在的代码如下:

final JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
final DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<>();
final StandardJavaFileManager fileManager = compiler.getStandardFileManager(diagnostics, null, null);
final Iterable<<? extends JavaFileObject>compilationUnits = fileManager.getJavaFileObjectsFromStrings(Arrays.asList("C:/test.java"));
final JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager, diagnostics, null, null, compilationUnits);

final boolean success = task.call();

try {
    System.out.println("Compiled: " + success);
    fileManager.close();
} catch (final IOException e) {
    e.printStackTrace();
}

但它也会在我不想要的文件夹中生成一个.class 文件。这段代码适用于 java 14 还是有更好的方法?

【问题讨论】:

标签: java


【解决方案1】:

首先,您可以稍微简化一下代码。您使用StandardJavaFileManager,它会导入接口Auto/Closeable。所以你可以使用try-with-resource:

final JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
final DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<>();

try (final StandardJavaFileManager fileManager = compiler.getStandardFileManager(diagnostics, null, null)) {
    final Iterable<<? extends JavaFileObject> compilationUnits = fileManager.getJavaFileObjectsFromStrings(Arrays.asList("C:/test.java"));
    final JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager, diagnostics, null, null, compilationUnits);

    final boolean success = task.call();
} catch (IOException e) {
    e.printStackTrace();
}

其次,您需要.class 文件来执行您的java 程序。有一个不错的post。如果要执行测试文件并使用命令行,则应使用以下命令:

javac Test.java
java Test

// or a little bit shorter
java Test.java

如果你想了解更多关于编译的信息,我可以推荐这个post 或者这个post

【讨论】:

    【解决方案2】:

    通过自己实现JavaFileObject 并“修补”JavaFileManager,您可以将编译器保留在内存中,然后通过暴露ClassLoaderdefineClass(),您可以加载生成的字节数组并通过反射启动main() .

    这是一个精简的变体,它实际上是按照您的要求进行的,从文件中加载源代码(无论如何,这是与 Files.readString() 的一行)。但是,这可能是您可以完全留给已修补的 JavaFileManager 的东西,只是我专注于将源代码保留在内存中,这就是为什么有注释的 String src = "..."; 块。

    import java.io.*;
    import java.net.*;
    import java.nio.file.*;
    import java.util.*;
    
    import javax.lang.model.element.*;
    import javax.tools.*;
    
    public class SOCompileTestDense {
      public static void main(String[] args) throws Exception {
        String name="Test";
    //    String src="public class Test {\r\n" + 
    //        "  public static void main(String[] args) {\r\n" + 
    //        "    System.out.println(\"Hello \"+args[0]+\"!\");\r\n" + 
    //        "  }\r\n" + 
    //        "}";
        String src=Files.readString(Paths.get(name+".java"));
        System.out.println(src);
        List<JFO> sources=Arrays.asList(new JFO(name,src));
        Map<String,JFO> files=new HashMap<>();
        JavaCompiler jc=ToolProvider.getSystemJavaCompiler();
        JavaCompiler.CompilationTask ct=jc.getTask(null, new JFM(jc.getStandardFileManager(null, null, null),files), null, null, null, sources);
        System.out.println(ct.call());
        Class<?> clazz=new CL().defineClass(name, files.get(name).baos.toByteArray());
        clazz.getDeclaredMethod("main", String[].class).invoke(null, (Object)new String[] {"StackOverflow"});
      }
      
      static class JFO implements JavaFileObject {
        final String name;
        final String src;
        JFO(String name,String src){
          this.name=name;
          this.src=src;
        }
    
        public URI toUri() {
          try {
            return new URI(name);
          } catch(URISyntaxException use) {use.printStackTrace();};
          return null;
        }
    
        public String getName() {
          return name;
        }
    
        ByteArrayOutputStream baos;
        public OutputStream openOutputStream() throws IOException {
          baos=new ByteArrayOutputStream();
          return baos;
        }
    
        public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
          return src;
        }
    
        public Kind getKind() {
          return Kind.SOURCE;
        }
    
        public boolean isNameCompatible(String simpleName, Kind kind) {
          return name.equals(simpleName);
        }
    
        public InputStream openInputStream() throws IOException {return null;}
        public Reader openReader(boolean ignoreEncodingErrors) throws IOException {return null;}
        public Writer openWriter() throws IOException {return null;}
        public long getLastModified() {return 0;}
        public boolean delete() {return false;}
        public NestingKind getNestingKind() {return null;}
        public Modifier getAccessLevel() {return null;}
      }
      
      static class JFM implements JavaFileManager {
        final JavaFileManager jfm;
        final Map<String, JFO> files;
        JFM(JavaFileManager jfm, Map<String, JFO> files){
          this.jfm=jfm;
          this.files=files;
        }
    
        public int isSupportedOption(String option) {
          return jfm.isSupportedOption(option);
        }
    
        public ClassLoader getClassLoader(Location location) {
          return jfm.getClassLoader(location);
        }
    
        public Iterable<JavaFileObject> list(Location location, String packageName, Set<JavaFileObject.Kind> kinds, boolean recurse) throws IOException {
          return jfm.list(location, packageName, kinds, recurse);
        }
    
        public String inferBinaryName(Location location, JavaFileObject file) {
          return jfm.inferBinaryName(location, file);
        }
    
        public boolean handleOption(String current, Iterator<String> remaining) {
          return jfm.handleOption(current, remaining);
        }
    
        public boolean hasLocation(Location location) {
          return jfm.hasLocation(location);
        }
    
        public JavaFileObject getJavaFileForInput(Location location, String className, JavaFileObject.Kind kind) throws IOException {
          return jfm.getJavaFileForInput(location, className, kind);
        }
    
        public JavaFileObject getJavaFileForOutput(Location location, String className, JavaFileObject.Kind kind, FileObject sibling) throws IOException {
          JFO jfo=new JFO(className,null);
          files.put(className, jfo);
          return jfo;
        }
        
          public String inferModuleName(Location location) throws IOException {
            return jfm.inferModuleName(location);
          }
    
          public Iterable<Set<Location>> listLocationsForModules(Location location) throws IOException {
            return jfm.listLocationsForModules(location);
          }
          
        public boolean isSameFile(FileObject a, FileObject b) {return false;}
        public FileObject getFileForInput(Location location, String packageName, String relativeName) throws IOException {return null;}
        public FileObject getFileForOutput(Location location, String packageName, String relativeName, FileObject sibling) throws IOException {return null;}
        public void flush() throws IOException {}
        public void close() throws IOException {}
      }
      
      static class CL extends ClassLoader {
        public Class<?> defineClass(String name,byte array[]) {
          return defineClass(name, array, 0, array.length);
        }
      }
    }
    

    这是一个整洁、简短的版本,它的输出如下所示:

    public class Test {
      public static void main(String[] args) {
        System.out.println("Hello "+args[0]+"!");
      }
    }
    true
    Hello StackOverflow!
    

    Ideone 上有一个更长的输出变体,https://ideone.com/DAXIlH,如果你下载那个变体并删除各种System.out.println(); 调用前面的//,它会显示更多的东西,它们是对 Ideone 来说太过分了。
    实际上,我所做的是将所有方法实现为throw new Error();(参见 Ideone 上的代码),并在实际调用它们时逐渐用有意义的东西填充它们。实际上它非常简单,我遇到的唯一问题是JavaFileManager 接口的一对default 方法。由于 eclipse 没有提供这些的 stub 实现,当代码在没有触及我的 throw new Error(); 行的情况下死掉时,这有点令人惊讶。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-04-10
      • 1970-01-01
      • 2014-02-27
      相关资源
      最近更新 更多