【问题标题】:Is there away to generate Variables' names dynamically in Java?是否可以在 Java 中动态生成变量名称?
【发布时间】:2010-11-14 14:52:11
【问题描述】:

假设我需要生成变量来保存用户的一些输入(我不知道它们有多少)。如果不使用ArrayArrayList(和其他类型的列表和映射),我的代码可以生成(比如说)String 变量 X 次,名称类似于(String var001String var002String var003 等) ?如果是,请提供示例代码。

【问题讨论】:

  • 这是一个非常奇怪的要求...你是什么意思 - “不知道有多少”?你什么时候知道?在运行时?无论如何,这看起来真的像一个收集类的工作。为什么你不能使用它们?你能告诉我们更多关于你的问题吗?
  • 我知道这是一个奇怪的要求,但这是我们教授提出的一个具有挑战性的问题。所以我更关心的是不管它的可用性如何做它的可能性!我的小组发现的唯一技巧是“Markus Lausberg”已经就这个问题提出了建议......那么还有其他方法吗?
  • 我希望你的教授没有读到堆栈溢出!他/她叫什么名字?
  • 我认为你的教授的意思是反思。我从未使用过 Java 反射,但通过 API,您不会将标识符与 Java 中的反射创建的对象实例相关联。这里肯定有什么误解。
  • 询问您的教授是否也希望您蒙上眼睛进行编程 - 这也很有意义。数组和集合正是为了这个目的而存在的。我什至看不出你能从这样的练习中学到什么有用的东西。

标签: java dynamic code-generation names


【解决方案1】:

如果您真的想做这样的事情,您可以使用ASM 或其他库通过字节码生成来实现。

下面的代码将生成一个名为“foo.bar.ClassWithFields”的类,其中包含字段“var0”到“var99”。当然,除了反射之外没有其他方法可以访问这些字段,因为它们在编译时不存在,而 Java 是一种静态类型语言。

import org.objectweb.asm.*;
import static org.objectweb.asm.Opcodes.*;

import java.lang.reflect.Field;

public class GeneratedFieldsExperiment {

    public static byte[] generateClassWithFields(int fieldCount) throws Exception {
        ClassWriter cw = new ClassWriter(0);
        FieldVisitor fv;
        MethodVisitor mv;
        AnnotationVisitor av0;

        cw.visit(V1_6, ACC_PUBLIC + ACC_SUPER, "foo/bar/ClassWithFields", null, "java/lang/Object", null);

        for (int i = 0; i < fieldCount; i++) {
            fv = cw.visitField(ACC_PUBLIC, "var" + i, "Ljava/lang/String;", null, null);
            fv.visitEnd();
        }
        {
            mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
            mv.visitCode();
            mv.visitVarInsn(ALOAD, 0);
            mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V");
            mv.visitInsn(RETURN);
            mv.visitMaxs(1, 1);
            mv.visitEnd();
        }
        cw.visitEnd();

        return cw.toByteArray();
    }

    public static void main(String[] args) throws Exception {
        MyClassLoader loader = new MyClassLoader();
        Class<?> c = loader.defineClass("foo.bar.ClassWithFields", generateClassWithFields(100));

        System.out.println(c);
        System.out.println("Fields:");
        for (Field field : c.getFields()) {
            System.out.println(field);
        }
    }

    private static class MyClassLoader extends ClassLoader {
        public Class<?> defineClass(String name, byte[] b) {
            return defineClass(name, b, 0, b.length);
        }
    }
}

【讨论】:

  • 它使用 ASM 库生成一个类 - 字节数组与 Java 编译器生成的 .class 文件格式相同 - 然后使用自定义类加载器将其加载到 JVM。 ASM 在 Java 字节码级别工作(类似于汇编代码),因此要理解上述代码,您首先需要学习一些 Java 字节码(ASM 的文档对此很有帮助:download.forge.objectweb.org/asm/asm-guide.pdf)。其他一些字节码操作库可能比 ASM 更容易使用,因为它们比纯字节码更高级别。我认为 Javassist 就是这样一个库。
【解决方案2】:

不使用数组,ArrayList(和 其他类型的列表和地图)

使用这些名称创建文件。希望这对你的教授有用。

或者使用前面提到的Java Scripting API:

ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("JavaScript");

engine.put("x", "hello"); // you can add any variable here
// print global variable "x"
engine.eval("println(x);");
// the above line prints "hello"

编辑

似乎在内部这将使用地图 :) 与属性文件、首选项 API 或 DOM 树相同(它们使用向量)。因此,如果您的教授如此挑剔,请使用文件。

【讨论】:

    【解决方案3】:

    我还没有看到这个答案,所以我会去的。编写一个只写出 Java 源代码的程序。其中大部分可能是一个模板,而您只需有一个循环,可以根据需要编写任意数量的“字符串 UserString003”类型变量。

    是的,这太可怕了。但是,正如您所说,这是作业的概念挑战问题,所以只要没有人将其误认为“好”代码,它可能会解决问题。

    【讨论】:

      【解决方案4】:

      以下是我实施并帮助我轻松解决我的解决方案而没有太多障碍的方式。

      // 创建数组List

      List accountList = new ArrayList(); 
      
      
      
      
      for(int k=0;k < counter;k++){
              accountList.add(k, (String)flowCtx.getValueAt("transitId"+m));
      }
      

      迭代循环并将对象添加到带有索引的数组列表中。

      //在运行时借助索引检索对象

      String a = accountList.get(i));
      

      【讨论】:

      • 这里的 flowCtx 是什么?
      【解决方案5】:

      像这样命名变量看起来很像 1980 年。意思是面向对象编程之前。 因此,如果您以构建软件为生 - 不要这样做。

      不过好像是功课……

      当我们谈论 Java 中的命名变量时,我们指的是已编译的东西。与某些脚本语言不同,Java 中没有简单的方法可以做到这一点。

      因此,要么使用 Markus Lausberg 建议的运行时编译类。
      或者你作弊并使用Java Scripting API 并使用脚本语言。这样您就可以在运行时创建代码(在字符串中)。

      【讨论】:

      • Java Scripting API 非常有趣 :) 感谢您提供新信息!
      【解决方案6】:

      我认为你可以在运行时生成一个 Java 类,或者使用像Beanshell 这样的脚本引擎来生成变量,你甚至可以通过它的字节码来构建这个类。但是我看不出你将如何在代码中使用这些变量,你还必须创建代码来使用这些变量,或者为此使用反射......

      一个幼稚的解决方案:
      创建一个包含从 var000 到 var999 的所有变量的类,每个变量都有一个 getter……但这并不是真正动态的!

      【讨论】:

        【解决方案7】:

        看起来你的教授在这个特性上偏向 PHP (Variable variables),所以他在想这在 java 中是否可行。

        我个人认为这是不可能的,而不是按照您提议的方式。可以做的是在运行时生成类,使用Javassist之类的工具,做出更强大的反射机制。因此,您可以在运行时创建一个包含所需变量(string1、string2 等)的类。

        但是,不要忘记Variable variables 是一种非常糟糕的技术,它会导致糟糕的代码。它可能在极少数情况下有用,但我真的不推荐它。

        【讨论】:

          【解决方案8】:

          你的意思是你想生成变量命名

          var0、var1、var2 并在您的代码中使用它们。

          使用时有什么区别 变量[0],变量[1],变量[2],.....

          但是

          您可以在运行时动态生成一个 Java 类,该类实现您在普通代码中使用的接口。然后使用编译器(例如 Janino)编译该类,然后在运行时加载该类。比你动态创建一个类。

          但我想知道,这对于您的用例是否有必要。

          编辑

          我现在不知道您在哪个用例中使用此参数,但您可以在 Java 中使用动态参数,例如 example from here

          // calculate average
                  public static double average( double... numbers )
                  {
                     double total = 0.0; // initialize total
          
                    // calculate total using the enhanced for statement
                    for ( double d : numbers )              
                       total += d;                          
          
                    return total / numbers.length;
                 } // end method average
          

          【讨论】:

            【解决方案9】:

            这是不可能的,但这是使用其中一个 java 集合的完美候选。

            要么使用动态分配的数组:

            String[] arr = new String[RUNTIME_SIZE];
            

            或者一个可以在运行时改变其大小的列表:

            List list = new ArrayList<String>();
            

            【讨论】:

            • 为了记录,它可能在 PHP 中。请参阅手册中的“变量”。
            【解决方案10】:

            我不知道我是否理解正确,但如果您尝试为变量使用动态创建的名称,那么是的,当然 - 我这样做是这样的:

            // rndRng() creates random numbers in specified range
            // this would output dynamically created variable like "name89"
            String myDynamicalyCreatedName = "name" + Utils.rndRng(0, 100);
            final UberShader $myDynamicalyCreatedName = new UberShader();
            

            正如您所看到的,这里的关键点是符号“$”,它基本上表示“从该符号之后给出的字符串创建变量名”,基本上就是这样 - 对我来说就像一个魅力几年现在...希望这是您想要的,它有助于解决您的问题。

            【讨论】:

            • 我认为 Java 语言的工作方式与您想象的不同。您可以删除定义myDynamicallyCreatedName 的行,代码仍然会执行相同的操作。它只是创建了一个名为 $myDynamicalyCreatedName 的变量。
            • 好吧,奇怪的是它确实有效:我的一些代码正在创建材料(例如具有特定表面属性的颜色 - 金属、闪亮、玻璃等),我有 3 种不同颜色的特定材料(比方说“chrome”),每种材料都必须有唯一的名称 - 现在我对所有 chrome 材料使用相同的脚本,并且每个创建的都有其独特的动态创建名称,就像我上面描述的那样......现在告诉我出了什么问题正如我所见,它每天都在工作! ;-)
            • 也许您没有使用 Java?如果动态创建的名称以这种方式工作,您应该可以这样做:String name = "myVar"; int $name = 1; System.out.println(myVar); 但这甚至无法编译。
            • 看,我不是 JAVA 大师,所以请不要给我这个反问的问题,我不知道为什么或如何,但它肯定对我有用 - 一切都顺利编译并且像魅力一样工作,如果不是同一类的每个新材料(在本例中为 Chrome)都会覆盖以前没有的材料:所有材料都有自己唯一的名称,或者更好地说不是像 .setName() 这样的名称,而是初始化时的“名称”他们(英语不是我的母语,抱歉)...顺便说一句,我宁愿尝试 System.out.println($name);
            • 我认为您正在创建一个名为 $myDynamicalyCreatedName 的变量。 myDynamicalyCreatedName 的值无关紧要。事实上,myDynamicalyCreatedName 甚至不需要存在。
            猜你喜欢
            • 2018-08-05
            • 2020-07-17
            • 1970-01-01
            • 2012-10-28
            • 1970-01-01
            • 2019-08-24
            • 2016-05-16
            相关资源
            最近更新 更多