【问题标题】:Java: newInstance of class that has no default constructorJava:没有默认构造函数的类的newInstance
【发布时间】:2011-04-09 22:56:58
【问题描述】:

我正在尝试为学生的家庭作业构建一个自动测试框架(基于 jUnit,但这并不重要)。他们必须为某些类创建构造函数,并为它们添加一些方法。稍后,通过我提供的测试功能,他们会检查是否正常。

我想要做的是,通过反射,为我想要测试的某个类创建一个新实例。问题在于,有时没有默认构造函数。我不在乎,我想自己创建一个实例并初始化实例变量。有没有办法做到这一点? 很抱歉,如果以前有人问过这个问题,但我找不到任何答案。

提前致谢。

【问题讨论】:

    标签: java reflection constructor new-operator instance


    【解决方案1】:

    调用Class.getConstructor() 然后Constructor.newInstance() 传入适当的参数。示例代码:

    import java.lang.reflect.*;
    
    public class Test {
    
        public Test(int x) {
            System.out.println("Constuctor called! x = " + x);
        }
    
        // Don't just declare "throws Exception" in real code!
        public static void main(String[] args) throws Exception {
            Class<Test> clazz = Test.class;
            Constructor<Test> ctor = clazz.getConstructor(int.class);
            Test instance = ctor.newInstance(5);           
        }
    }
    

    【讨论】:

    • 这将涉及一些混乱的反射来获取一个构造函数,并遍历它,为每个参数提供一个适当的值......
    • 谢谢。问题是我不知道他们是否已经添加了构造函数。当然,我可以通过捕获适当的异常来检查他们是否这样做。但我不知道他们是否使用正确的参数创建了构造函数。更糟糕的是,我不知道构造函数是否工作正常。我想构建实例而不依赖于它们的实现。
    • @GermanK:然后改用 Class.getConstructors() 看看有什么可用的。你必须依赖一个实现来实例化一个类。如果您创建一个实例而不使用适当的参数调用它们的构造函数之一,那么您对它们的类​​不公平,它们会期望被正确实例化。我建议你授权一个特定的签名。
    • @GermanK 然后在你的测试方法的末尾有一个可变参数,用户输入必要的参数
    • @GermanK,你是教授。如果学生没有正确完成作业,则学生失败。向他们反馈他们失败的原因。下一次,他们会更加小心。
    【解决方案2】:

    这是一个不需要 javassist 或其他字节码“操纵器”的通用解决方案。虽然,它假设构造函数除了简单地将参数分配给相应的字段之外没有做任何其他事情,所以它只是选择第一个构造函数并创建一个具有默认值的实例(即,0 代表 int,null 代表 Object 等)。

    private <T> T instantiate(Class<T> cls, Map<String, ? extends Object> args) throws Exception
    {
        // Create instance of the given class
        final Constructor<T> constr = (Constructor<T>) cls.getConstructors()[0];
        final List<Object> params = new ArrayList<Object>();
        for (Class<?> pType : constr.getParameterTypes())
        {
            params.add((pType.isPrimitive()) ? ClassUtils.primitiveToWrapper(pType).newInstance() : null);
        }
        final T instance = constr.newInstance(params.toArray());
    
        // Set separate fields
        for (Map.Entry<String, ? extends Object> arg : args.entrySet()) {
            Field f = cls.getDeclaredField(arg.getKey());
            f.setAccessible(true);
            f.set(instance, arg.getValue());
        }
    
        return instance;
    }
    

    附:适用于 Java 1.5+。该解决方案还假设没有可以阻止调用 f.setAccessible(true) 的 SecurityManager 管理器。

    【讨论】:

    • 这很好,但我认为应该是:params.add((pType.isPrimitive()) ? 0 : null);
    • @NT_ 好地方。虽然简单地传递零是行不通的,但需要正确的类型。 newInstance() 将在将 pType 转换为包装类后起作用(例如,可以使用来自 apache-commons 的 ClassUtils)。
    • 呃,你什么意思?它似乎对我有用。编译器将执行必要的缩小/扩展和装箱,0 将转换为所有原语的默认值。我已经使用了很长一段时间没有问题...
    • 编译器无法捕捉到它,因为 pType 的真实类型仅在运行时才知道,而构造函数参数类型匹配是在运行时完成的。可能您将它与兼容的类型(例如 int)一起使用,尝试使用“char”类型的字段。
    【解决方案3】:

    如果您没有使用过模拟框架(例如 ezmock),我强烈建议您尝试一下。

    我可能是错的,这可能对你没有任何帮助,但从我从你的帖子中收集到的信息来看,嘲笑可能正是你正在寻找的(尽管我承认它与你要求什么。

    编辑:回应评论。

    不,现代模拟框架允许您从“无”创建任何类的“假”实例,并将其作为类的实例传递。它不需要接口,它可以是任何类。还可以编写方法以返回一系列值,从简单的始终返回“7”到“使用 arg=7 调用时,第一次调用返回 5,第二次调用返回 6,第三次调用返回 7”。

    它通常与测试框架结合使用,以提供参考类以传递给您正在测试的类。

    这可能不是您想要的,但您提到了单元测试和手动初始化变量,所以看起来这最终可能会派上用场。

    【讨论】:

    • 我认为这需要一些模拟框架将实现的接口,对吧?因为我没有接口......它们是非常简单的类,学生将实现这些类。
    【解决方案4】:

    我使用以下代码创建传入的任何类型的类名的通用对象列表。它使用类中的所有 set 方法来设置通过结果集传入的所有值。如果有人也对它感兴趣,我会发布这个。

    protected List<Object> FillObject(ResultSet rs, String className)
        {
            List<Object> dList = new ArrayList<Object>();
    
            try
            {
                ClassLoader classLoader = GenericModel.class.getClassLoader();
    
                while (rs.next())
                {
                    Class reflectionClass = classLoader.loadClass("models." + className);
    
                    Object objectClass = reflectionClass.newInstance();
    
                    Method[] methods = reflectionClass.getMethods();
    
                    for(Method method: methods)
                    {
                        if (method.getName().indexOf("set") > -1)
                        {
                            Class[] parameterTypes = method.getParameterTypes();
    
                            for(Class pT: parameterTypes)
                            {
                                Method setMethod = reflectionClass.getMethod(method.getName(), pT);
    
                                switch(pT.getName())
                                {
                                    case "int":
                                        int intValue = rs.getInt(method.getName().replace("set", ""));
                                        setMethod.invoke(objectClass, intValue);
                                        break;
    
                                    case "java.util.Date":
                                        Date dateValue = rs.getDate(method.getName().replace("set", ""));
                                        setMethod.invoke(objectClass, dateValue);
                                        break;
    
                                    case "boolean":
                                        boolean boolValue = rs.getBoolean(method.getName().replace("set", ""));
                                        setMethod.invoke(objectClass, boolValue);
                                        break;
    
                                    default:
                                        String stringValue = rs.getString(method.getName().replace("set", ""));
                                        setMethod.invoke(objectClass, stringValue);
                                        break;
                                }
                            }
                        }
                    }
    
                    dList.add(objectClass);
                }
            }
            catch (Exception e)
            {
                this.setConnectionMessage("ERROR: reflection class loading: " + e.getMessage());
            }
    
            return dList;
        }
    

    【讨论】:

      【解决方案5】:

      您可以使用Class.getConstructorClass.getConstructors,然后使用Constructor.newInstance方法来初始化您要使用的对象。

      【讨论】:

      • 使用没有参数的 Class.getConstructor 或 Class.getDeclaredConstructor 如果没有声明默认构造函数,则会得到 java.lang.NoSuchMethodException
      • @GermanK 如果是这样的话,我想知道你为什么接受这个答案。如果你没有用int 参数类型声明构造函数,它会抛出同样的异常
      • @Farid 我猜区别在于参数,但谁记得 9 年后:)
      【解决方案6】:

      您可以随作业分发以下源代码。告诉学生将其包含在他们的源代码中。他们的代码不会编译,除非他们用正确的签名编写一个 Assignment 类。编译器会为您进行签名检查。

      那么你的测试程序就不需要使用反射了。只需实例化一个 AssignmentFactory 并使用适当的参数调用 make 方法。

      如果你使用这个想法,你的新挑战将是一些学生修改 AssignmentFactory 以适应他们的 Assignment 课程(破坏你的测试程序)。

      package assignment ;
      
      public class AssignmentFactory
      {
           public AssignmentFactory ( )
           {
                 super ( ) ;
           }
      
           public AssignmentFactory make ( .... parameters )
           {
                 return new Assignment ( .... arguments ) ;
           }
      }
      

      【讨论】:

      • 这只是测试(签名正确性)编译时间的一部分......如果他们没有正确初始化实例变量会发生什么?无论如何,我仍然需要测试它们。另一方面,我不想添加任何会分散他们在任务中主要目标的内容。
      • 是的,您仍然需要评估他们的作业。 AssignmentFactory 的目的是试图强制他们以适合编程评估的格式提交作业。
      猜你喜欢
      • 2022-01-01
      • 1970-01-01
      • 2023-03-20
      • 2015-07-07
      • 2012-09-01
      • 1970-01-01
      • 2016-07-18
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多