4

我知道 eval 是“邪恶的”,但我以一种用户永远不能滥用它的方式使用它。

假设我有一个字符串“new Integer(5)”。我想做一些事情,这样我就可以将一个变量(比如说 foo)设置为 new Integer(5)。就像是

Integer foo;
String bar = "new Integer(5)"
*magic happens*
System.out.println(foo) -> 5

我环顾四周,看起来我有几个选择。ToolProvider 中的 getSystemJavaCompiler() 方法可以做到这一点吗?还是我应该使用 BeanShell?或者还有别的什么?请注意,这是来自字符串,而不是文件。

4

7 回答 7

3

这是一种通过使用 javax.tools 获得大部分方法的可能方法。请注意,这段代码相当长,并不是最有效或最便携的方法,但您应该明白这一点。

import javax.tools.*;
import java.util.*;
import java.io.*;
import java.lang.reflect.*;

public class Test {
  public static void main(String[] args) throws Exception {
    JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
    JavaFileObject fileObj = 
      new StringJavaFileObject("public class InterpTest { public static void test() { System.out.println(\"Hello World\"); } }");
    List<JavaFileObject> tasks = new ArrayList<JavaFileObject>();
    tasks.add(fileObj);

    JavaFileManager defFileMgr = compiler.getStandardFileManager(null, null, null);
    MemoryJavaFileManager fileMgr = new MemoryJavaFileManager(defFileMgr);
    compiler.getTask(null, fileMgr, null, null, null, tasks).call();

    ClassLoader loader = new ByteArrayClassLoader();
    Class clazz = loader.loadClass("InterpTest");
    Method method = clazz.getMethod("test");
    method.invoke(null);
  }

  public static class StringJavaFileObject extends SimpleJavaFileObject {
    protected String str;

    public StringJavaFileObject(String str) {
      super(java.net.URI.create("file:///InterpTest.java"), JavaFileObject.Kind.SOURCE);
      this.str = str;
    }

    @Override
    public CharSequence getCharContent(boolean ignoreEncErrors) {
      return str;
    }
  }

  public static class MemoryJavaFileObject extends SimpleJavaFileObject {
    public static ByteArrayOutputStream out = new ByteArrayOutputStream();

    public MemoryJavaFileObject(String uri, JavaFileObject.Kind kind) {
      super(java.net.URI.create(uri), kind);
    }

    @Override
    public OutputStream openOutputStream() {
      return out;
    }
  }

  public static class ByteArrayClassLoader extends ClassLoader {
    public Class findClass(String name) {
      byte[] bytes = MemoryJavaFileObject.out.toByteArray();
      return super.defineClass(name, bytes, 0, bytes.length);
    }
  }

  public static class MemoryJavaFileManager implements JavaFileManager {
    protected JavaFileManager parent;

    public JavaFileObject getJavaFileForOutput(JavaFileManager.Location location, String className, JavaFileObject.Kind kind, FileObject sibling) throws IOException {
      return new MemoryJavaFileObject("file:///InterpTest.class", kind);
    }

    public MemoryJavaFileManager(JavaFileManager parent) { this.parent = parent; }
    public void close() throws IOException { parent.close(); }
    public void flush() throws IOException { parent.flush(); }
    public ClassLoader getClassLoader(JavaFileManager.Location location) { return parent.getClassLoader(location); }
    public FileObject getFileForInput(JavaFileManager.Location location, String packageName, String relName) throws IOException { return parent.getFileForInput(location, packageName, relName); }
    public FileObject getFileForOutput(JavaFileManager.Location location, String packageName, String relName, FileObject sibling) throws IOException { return parent.getFileForOutput(location, packageName, relName, sibling); }
    public JavaFileObject getJavaFileForInput(JavaFileManager.Location location, String className, JavaFileObject.Kind kind) throws IOException { return parent.getJavaFileForInput(location, className, kind); }
    public boolean handleOption(String current, Iterator<String> remaining) { return parent.handleOption(current, remaining); }
    public boolean hasLocation(JavaFileManager.Location location) { return parent.hasLocation(location); }
    public String inferBinaryName(JavaFileManager.Location location, JavaFileObject file) { return parent.inferBinaryName(location, file); }
    public boolean isSameFile(FileObject a, FileObject b) { return parent.isSameFile(a, b); }
    public Iterable<JavaFileObject> list(JavaFileManager.Location location, String packageName, Set<JavaFileObject.Kind> kinds, boolean recurse) throws IOException { return parent.list(location, packageName, kinds, recurse); }
    public int isSupportedOption(String option) { return parent.isSupportedOption(option); }
  }
}
于 2009-07-23T05:30:13.977 回答
3

我会使用脚本语言,如 beanshell、jruby、jython 等。

于 2009-07-23T00:16:01.930 回答
3

你必须使用类似Janino的东西。

于 2009-07-23T00:51:43.423 回答
2

这种事情是可能的,但对于如此简单的任务来说,这将是非常昂贵的。在这个例子中,我会考虑使用 Class.forName() 将“Integer”映射到一个类,Java 反射调用构造函数。

于 2009-07-23T02:16:37.717 回答
1

Integer 类在其构造函数中使用 String 来设置值,假设提供的字符串仅包含数字文本。

Integer foo;

public void setFoo(String str) {
   if(isInt(str)) {
     foo = new Integer(str.trim());
   }
}

// Returns a boolean based on if the provided string contains only numbers
private boolean isInt(String str) {
  boolean isInt = true;

  try {
    Integer.parseInt(str.trim());
  } catch (NumberFormatException nfe) {
    isInt = false;
  }

  return isInt;
}

// Get the value as an int rather than Integer
public int getIntValue(String str) {
  return Integer.parseInt(str.trim());
}
于 2009-07-23T00:37:33.403 回答
0

Java 是一种静态类型的语言,所以我认为你做不到。

于 2009-07-23T00:43:06.947 回答
0

您可以使用Java 脚本 API。默认语言是 JavaScript,但您可以插入任何语言。不过它需要java 1.6。

于 2009-07-23T10:26:29.263 回答