Java:没有默认构造函数的类的newInstance

44
我正在尝试为学生的作业构建一个自动化测试框架(基于jUnit,但这不重要)。他们需要为某些类创建构造函数并添加一些方法。后来,使用我提供的测试功能,他们将检查是否正确执行了操作。
我想做的是,通过反射创建我想要测试的某个类的新实例。问题是,有时候没有默认的构造函数。我不关心这一点,我想创建一个实例并手动初始化实例变量。有没有办法做到这一点? 如果此前已经问过这个问题,我很抱歉,我只是找不到任何答案。
谢谢。
6个回答

53

调用 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);           
    }
}

2
它将涉及一些混乱的反射来获取构造函数,并遍历它,为每个参数提供适当的值... - bwawok
4
请使用 Class.getConstructors() 方法查看可用的构造函数。您必须依赖某种实现才能实例化一个类。如果您在不使用适当参数调用其中任何一个构造函数的情况下创建一个实例,那么您就没有公平地使用它们的类,这些类会期望被正确地实例化。我建议您强制规定特定的签名。 - Jon Skeet
@Jon Skeet:我确实要求特定的签名。如果他们不尊重它怎么办?我有一个可行的选择是测试他们的构造函数,并告诉他们在正确构建该部分之前,他们将无法测试任何其他方法。如果没有更好的选择,我想这就是我会坚持的。 - GermanK
1
@GermanK,您是教授。如果学生没有正确完成作业,那么他们就失败了。给他们反馈,说明他们为什么失败。下次他们会更加小心。 - emory
啊,刚才是因为没有这个方法而引发了未处理的异常。我捕获了其他东西,所以以为已经解决了。 - Gunnar Bernstein
显示剩余6条评论

6

这里有一个通用的解决方案,不需要使用javassist或其他字节码“操作器”。尽管如此,它假设构造函数除了将参数分配给相应字段之外没有其他操作,因此它只选择第一个构造函数,并使用默认值(例如int为0,Object为null)创建实例。

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+。该解决方案还假设没有SecurityManager管理器可以防止调用f.setAccessible(true)


这很不错,但我认为它应该是: params.add((pType.isPrimitive()) ? 0 : null); - NT_
@NT_ 不错的发现。但是仅仅传递零值不行,因为需要正确的类型。在将pType转换为包装类之后,使用newInstance()将可行(例如,可以使用apache-commons中的ClassUtils)。 - Vlad
嗯,你是什么意思?对我来说它似乎是有效的。编译器将执行必要的缩小/扩展和装箱操作,并将0转换为所有基元的默认值。我已经使用这个方法有一段时间了,没有出现过问题... - NT_
编译器无法捕获它,因为pType的真实类型只有在运行时才知道,构造函数参数类型匹配也是在运行时完成的。可能您使用了兼容类型(例如int),请尝试使用类型为'char'的字段。 - Vlad

2
如果您还没有使用过模拟框架(如ezmock),我强烈建议您尝试一下。
虽然我可能错了,这可能对您没有帮助,但从您的帖子中我可以了解到,模拟可能正是您正在寻找的(尽管我认识到它与您所“要求”的内容无关)。
编辑:回应评论。
不,现代模拟框架允许您从“无”创建任何类的“伪”实例,并将其传递,就好像它是该类的实例一样。它不需要接口,它可以是任何类。方法也可以被编写为返回一系列值,从简单的始终返回“7”到“当使用arg = 7调用时,第一次调用返回5,第二次返回6,第三次返回7”。
它通常与测试框架一起使用,以提供一个参考类以传递给您正在测试的类。
这可能不完全是您要寻找的内容,但是您提到了单元测试和手动初始化变量,因此似乎这是一些可能会派上用场的东西。

我认为这需要一些接口,模拟框架会实现它,对吧?因为我没有接口……学生将要实现的是非常简单的类。 - GermanK

1

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

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;
    }

0

3
如果使用Class.getConstructor或者Class.getDeclaredConstructor不带参数,如果没有默认的构造函数声明,你会得到一个java.lang.NoSuchMethodException异常。 - GermanK
@GermanK 如果是这样的话,我想知道为什么你接受了这个答案。如果你没有声明一个带有“int”参数类型的构造函数,它将抛出相同的异常。 - Farid
1
@Farid 我猜差异在于参数,但是谁能记得9年后的事情呢 :) - GermanK

0
您可以将以下源代码与作业一起分发。告诉学生将其包含在他们的源代码中。除非他们编写一个带有正确签名的Assignment类,否则他们的代码将无法编译。编译器会为您执行签名检查。
然后,您的测试程序不需要使用反射。只需实例化AssignmentFactory并使用正确的参数调用make方法即可。
如果您使用此想法,则新的挑战将是一些学生修改AssignmentFactory以适合其Assignment类(破坏您的测试程序)。
package assignment ;

public class AssignmentFactory
{
     public AssignmentFactory ( )
     {
           super ( ) ;
     }

     public AssignmentFactory make ( .... parameters )
     {
           return new Assignment ( .... arguments ) ;
     }
}

这只是将测试的一部分(签名正确性)移到编译时进行... 如果它们没有正确初始化实例变量会发生什么?我仍然需要测试它们。另一方面,我不想添加任何会让他们在任务中分心的东西。 - GermanK
是的,您仍需要评估他们的作业。AssignmentFactory的目的是尝试强制他们以适合程序化评估的格式提交作业。 - emory

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接