web-dev-qa-db-fra.com

Java: newInstance de classe qui n'a pas de constructeur par défaut

J'essaie de créer un cadre de test automatique (basé sur jUnit, mais ce n'est pas important) pour les devoirs de mes élèves. Ils devront créer des constructeurs pour certaines classes et également leur ajouter des méthodes. Plus tard, avec les fonctions de test que je fournis, ils vérifieront s'ils se sont bien passés.

Ce que je veux faire est, par réflexion, créer une nouvelle instance d'une classe que je veux tester. Le problème est que, parfois, il n'y a pas de constructeur par défaut. Je m'en fiche, je veux créer une instance et initialiser moi-même les variables d'instance. Y a-t-il une manière de faire ça? Je suis désolé si cela a déjà été demandé, mais je n'ai trouvé aucune réponse.

Merci d'avance.

39
GermanK

Appelez Class.getConstructor() puis Constructor.newInstance() en passant les arguments appropriés. Exemple de code:

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);           
    }
}
50
Jon Skeet

Voici une solution générique qui ne nécessite pas javassist ou un autre "manipulateur" de bytecode. Bien que cela suppose que les constructeurs ne font rien d'autre que d'attribuer simplement des arguments aux champs correspondants, il choisit simplement le premier constructeur et crée une instance avec des valeurs par défaut (c'est-à-dire 0 pour int, null pour Object, etc.).

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

P.S. Fonctionne avec Java 1.5+. La solution suppose également qu'aucun gestionnaire SecurityManager ne pourrait empêcher l'invocation de f.setAccessible(true).

6
Vlad

Si vous n'avez pas utilisé de frameworks moqueurs (comme ezmock), je vous recommande fortement de l'essayer.

Je peux me tromper et cela peut ne pas vous aider du tout, mais d'après ce que j'ai pu recueillir de votre message, il semble possible que la moquerie soit exactement ce que vous recherchez (même si je reconnais que cela n'a rien à voir avec ce que vous - demandé pour.

Edit: En réponse au commentaire.

Non, les cadres de simulation modernes vous permettent de créer une "fausse" instance de n'importe quelle classe à partir de "rien" et de la transmettre comme si c'était une instance de la classe. Il n'a pas besoin d'interface, il peut s'agir de n'importe quelle classe. Des méthodes peuvent également être scriptées pour renvoyer une séquence de valeurs à partir d'un simple toujours retourner "7" à "Lorsqu'il est appelé avec un argument = 7, renvoyer 5 le premier appel, 6 le deuxième et 7 le troisième".

Il est généralement utilisé en conjonction avec les frameworks de test pour donner une classe de référence à passer à la classe que vous testez.

Ce n'est peut-être pas exactement ce que vous recherchez, mais vous avez mentionné les tests unitaires et l'initialisation manuelle des variables, il semblait donc que cela pourrait éventuellement être utile.

2
Bill K

J'ai utilisé le code suivant pour créer une liste d'objets génériques de tout type de nom de classe transmis. Il utilise toutes les méthodes définies dans la classe pour définir toutes les valeurs transmises via le jeu de résultats. Je poste ceci au cas où quelqu'un s'y intéresserait également.

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

Vous pouvez distribuer le code source suivant avec votre affectation. Dites aux élèves de l'inclure dans leur code source. Leur code ne se compilera que s'il code une classe d'assignation avec la signature appropriée. Le compilateur vérifie la signature pour vous.

Ensuite, votre programme de test n'a pas besoin d'utiliser la réflexion. Il suffit d'instancier une AssignmentFactory et d'appeler la méthode make avec les arguments appropriés.

Si vous utilisez cette idée, votre nouveau défi sera que certains étudiants modifient AssignmentFactory pour s'adapter à leur classe de devoir (rompant votre programme de test).

package assignment ;

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

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

Vous pouvez utiliser Class.getConstructor ou Class.getConstructors puis utiliser la méthode Constructor.newInstance pour initialiser l'objet que vous souhaitez utiliser.

0
Dani Cricco