2010-09-08 22 views
36

Estoy intentando construir un marco de prueba automático (basado en jUnit, pero eso no es importante) para la tarea de mis alumnos. Tendrán que crear constructores para algunas clases y también agregarles algunos métodos. Más tarde, con las funciones de prueba que proporciono, verificarán si funcionó bien.Java: nuevoInstancia de clase que no tiene un constructor predeterminado

Lo que quiero hacer es, por reflexión, crear una nueva instancia de alguna clase que quiero probar. El problema es que, a veces, no hay un constructor predeterminado. No me importa eso, Quiero crear una instancia e inicializar las variables de instancia yo mismo. ¿Hay alguna forma de hacer esto? Disculpa si esto se ha preguntado antes, pero simplemente no he podido encontrar ninguna respuesta.

Gracias de antemano.

Respuesta

47

Llame Class.getConstructor() y luego Constructor.newInstance() pasando los argumentos apropiados. Código de ejemplo:

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

implicará una reflexión sucio para conseguir un constructor, y caminar, dándole un valor apropiado para cada argumento ... Gracias – bwawok

+0

. El problema es que no sé si ya agregaron el constructor o no. Por supuesto, podría verificar si lo hicieron atrapando la excepción apropiada. Pero no sabría si crearon el constructor con los argumentos correctos. Peor aún, no sé si el constructor funciona bien. Me gustaría construir la instancia sin depender de su implementación. – GermanK

+4

@GermanK: use Class.getConstructors() y vea qué hay disponible. * Tienes * que depender de una implementación para crear una instancia de una clase. Si crea una instancia sin llamar a uno de sus constructores con argumentos apropiados, no estará jugando limpio con sus clases, lo que * esperaría * que se creara una instancia adecuada. Sugiero que * mandato * una firma particular. –

2

Si no ha utilizado marcos de burla (como ezmock), recomiendo que pruebe.

Puedo estar equivocado y eso puede no ayudarlo en absoluto, pero por lo que pude deducir de su publicación, parece posible que la burla sea exactamente lo que está buscando (aunque reconozco que no tiene nada que ver con lo que pedirá para

Editar:. En respuesta al comentario

No, marcos burlones modernos permiten crear una instancia de "falso" de cualquier clase de "nada" y pasa a su alrededor, como si. era una instancia de la clase. No necesita una interfaz, puede ser cualquier clase. También se pueden escribir métodos para generar una secuencia de valores desde un retorno siempre simple "7" a " Cuando se llama con un arg = 7, devuelve 5 la primera llamada, 6 la segunda y 7 la tercera ".

Se usa generalmente junto con los marcos de prueba para dar una clase de referencia para pasar a la clase que está probando.

Esto puede no ser exactamente lo que está buscando, pero mencionó las pruebas unitarias y las variables de inicialización manual, por lo que parecía que esto es algo que eventualmente puede ser útil.

+0

, creo que esto requiere alguna interfaz que implementará el framework de burlas, ¿no? Porque no tengo interfaces ... Son clases muy simples, las que los estudiantes implementarán. – GermanK

+0

OK, gracias, podría ser útil en otro momento. – GermanK

0

Puede distribuir el siguiente código fuente con su tarea. Indique a los alumnos que lo incluyan en su código fuente. Su código no se compilará a menos que codifique una clase de Asignación con la firma adecuada. El compilador hace la verificación de la firma por usted.

Luego su programa de prueba no necesita usar reflexión. Simplemente crea una instancia de AssignmentFactory y llama al método make con los argumentos adecuados.

Si usa esta idea, su nuevo desafío será que algunos estudiantes modifiquen AssignmentFactory para que se ajuste a su clase de Asignación (rompiendo su programa de prueba).

package assignment ; 

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

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

Esto solo formará parte de la prueba (corrección de la firma) para compilar el tiempo ... ¿Qué pasaría si no inicializan correctamente las variables de instancia? De todos modos, todavía tendría que probarlos. Por otro lado, no quiero agregar nada que los distraiga de su objetivo principal en su asignación. – GermanK

+0

Sí, aún necesitaría evaluar su asignación. El objetivo de AssignmentFactory es tratar de obligarlos a enviar su tarea en un formato adecuado para la evaluación programática. – emory

5

Aquí hay una solución genérica que no requiere javassist u otro bytecode "manipulador".Aunque asume que los constructores no hacen otra cosa que simplemente asignar argumentos a los campos correspondientes, por lo que simplemente selecciona el primer constructor y crea una instancia con valores predeterminados (es decir, 0 para int, null para 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. Funciona con Java 1.5+. La solución también asume que no hay un administrador de SecurityManager que pueda evitar la invocación de f.setAccessible(true).

+0

esto es bueno, pero creo que debería ser: params.add ((pType.isPrimitive())? 0: null); –

+0

@NT_ Buen lugar. Aunque simplemente pasar cero no funcionará, ya que se requiere un tipo correcto. newInstance() funcionará después de convertir pType a una clase de contenedor (por ejemplo, ClassUtils de apache-commons se puede usar). – Vlad

+0

Uhm, ¿qué quieres decir? Parece que funciona para mí. El compilador hará el estrechamiento/expansión necesaria y el boxeo requerido y 0 convertirá al valor predeterminado de todas las primitivas. Estoy usando esto desde hace bastante tiempo sin problemas ... –

1

Utilicé el siguiente código para crear una lista de objetos genéricos de cualquier tipo de nombre de clase entregado. Utiliza todos los métodos establecidos dentro de la clase para establecer todos los valores pasados ​​a través del conjunto de resultados. Estoy publicando esto en caso de que a alguien también le interese.

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