2012-02-09 10 views
5

Con la reflexión de Java, uno puede obtener un constructor a través de getConstructor(klass, args).Usando Java Reflection, ¿cómo obtener el constructor de una clase especificando una clase derivada de los argumentos del constructor?

Sin embargo, cuando pasamos como args una clase derivada de la clase especificada en la firma del constructor, falla. ¿Cómo superar este problema?

Por ejemplo,

HashSet.class.getConstructor(new Class[]{ HashSet.class }); 

falla. Mientras

HashSet.class.getConstructor(new Class[]{ Collection.class }); 

tiene éxito.

Estoy buscando algo que podría usarse fácilmente en clojure. Por lo tanto, preferiría tener algo listo para usar y no tener que agregar funciones definidas por el usuario.

¿Alguna idea de cómo solucionar este problema?

Respuesta

4

Sobre la base de las respuestas de esaj y T.J. Crowder:

Las siguientes declaraciones de una seq de constructores de la clase dada, que son: (1) se puede llamar con los tipos de argumentos especificados y (2) óptima en el sentido de que su declarada los tipos de parámetros se eliminan mediante un número mínimo de pasos en la escala de herencia desde los tipos de argumentos especificados. (Por lo tanto, siempre se devolverá una coincidencia exacta, si hay dos constructores que requieren conversión desde algunos de los tipos de argumento especificados a sus tipos de abuelos, y no hay una coincidencia más cercana, ambos serán devueltos, si no hay constructores coincidentes en absoluto, se devolverá nil). Los tipos de argumentos primitivos se pueden especificar como símbolos o palabras clave (es decir, 'int/:int). Finalmente, los tipos primitivos se consideran equivalentes a sus contrapartes encuadradas.

Ejemplo:

user> (find-best-constructors java.util.HashSet [:int :float]) 
(#<Constructor public java.util.HashSet(int,float)>) 
user> (find-best-constructors java.util.HashSet [java.util.HashSet]) 
(#<Constructor public java.util.HashSet(java.util.Collection)>) 
user> (find-best-constructors java.util.HashSet [Integer]) 
(#<Constructor public java.util.HashSet(int)>) 

Uno podría desear para permitir la ampliación de conversiones numéricas; eso podría hacerse, p. agregando Integer ->Long etc. mapeos a convm y modificando la condición if en count-steps a continuación.

Aquí está el código:

(defn find-best-constructors [klass args] 
     (let [keym {:boolean Boolean/TYPE 
        :byte Byte/TYPE 
        :double Double/TYPE 
        :float Float/TYPE 
        :int  Integer/TYPE 
        :long Long/TYPE 
        :short Short/TYPE} 
       args (->> args 
         (map #(if (class? %) % (keyword %))) 
         (map #(keym % %))) 
       prims (map keym [:boolean :byte :double :float :int :long :short]) 
       boxed [Boolean Byte Double Float Integer Long Short] 
       convm (zipmap (concat prims boxed) (concat boxed prims)) 
       ctors (->> (.getConstructors klass) 
         (filter #(== (count args) (count (.getParameterTypes %)))) 
         (filter #(every? (fn [[pt a]] 
              (or (.isAssignableFrom pt a) 
               (if-let [pt* (convm pt)] 
                (.isAssignableFrom pt* a)))) 
              (zipmap (.getParameterTypes %) args))))] 
      (when (seq ctors) 
      (let [count-steps (fn count-steps [pt a] 
           (loop [ks #{a} cnt 0] 
            (if (or (ks pt) (ks (convm pt))) 
            cnt 
            (recur (set (mapcat parents ks)) (inc cnt))))) 
        steps (map (fn [ctor] 
           (map count-steps (.getParameterTypes ctor) args)) 
          ctors) 
        m (zipmap steps ctors) 
        min-steps (->> steps 
           (apply min-key (partial apply max)) 
           (apply max))] 
       (->> m 
        (filter (comp #{min-steps} (partial apply max) key)) 
        vals))))) 
+0

¡Guau! Un poco de código.¿Podría proporcionar un archivo clj ejecutable que demuestre cómo llamar a 'find-best-constructors' con' HashSet' por ejemplo. – viebel

+0

Bueno, hay tres ejemplos en la respuesta. Puede copiar las expresiones a la derecha de 'user>' en su propio archivo REPL/y verificar que los valores devueltos sean los que se anuncian. –

+0

De alguna manera, aparece un error al importar HashSet. Puede acceder a mi archivo en: [find-constructors.clj] (https://github.com/viebel/Learning/blob/master/clojure/find-constructors.clj). Su sintaxis ' – viebel

5

HashSettiene ningúnHashSet(HashSet) constructor, por lo que naturalmente no recibe uno cuando lo solicita. Tienes que trabajar a través de las clases compatibles con la asignación (al menos recorrer las supers, y probablemente las interfaces implementadas y sus supers) para encontrar una.

+0

¿Por qué no se puede hacer esto por la infraestructura de la introspección? Realmente necesito esta funcionalidad para un problema desafiante en clojure. – viebel

+0

No está disponible porque es difícil. Tendría que replicar todas las reglas relevantes de especificación de lenguaje Java que se implementan en javac. El código Reflector de Clojure omite un gran subconjunto de las posibles llamadas permitidas (por ejemplo, boxeo/unboxing de argumentos), de ahí la necesidad de proporcionar a veces sugerencias de tipo/lanzamiento cuando no es estrictamente necesario. –

+0

La dificultad de esta tarea debería ser evidente por el hecho de que Sun ni siquiera incluyó soporte para ella en su propia API de reflexión (de ahí el apoyo parcial de Clojure a través del reflector). –

0

No confundas el comportamiento polimórfico aquí. Debido a que está pasando Collection como valor concreto, no param type in (new Class [] {Collection}).

5

Aquí hay una forma bastante sencilla de hacerlo. El getConstructorForArgs -metodo recorre todos los constructores en una clase determinada, y comprueba si los parámetros del constructor coinciden con los parámetros dados (tenga en cuenta que los parámetros dados deben estar en el mismo orden que en el constructor). Las implementaciones de interfaces y subclases también funcionan, porque la "compatibilidad" se comprueba llamando al isAssignableFrom para el argumento del constructor (es el tipo de parámetro dado asignable al tipo de parámetro en el constructor).

public class ReflectionTest 
{ 
    public Constructor<?> getConstructorForArgs(Class<?> klass, Class[] args) 
    { 
     //Get all the constructors from given class 
     Constructor<?>[] constructors = klass.getConstructors(); 

     for(Constructor<?> constructor : constructors) 
     { 
      //Walk through all the constructors, matching parameter amount and parameter types with given types (args) 
      Class<?>[] types = constructor.getParameterTypes(); 
      if(types.length == args.length) 
      {    
       boolean argumentsMatch = true; 
       for(int i = 0; i < args.length; i++) 
       { 
        //Note that the types in args must be in same order as in the constructor if the checking is done this way 
        if(!types[i].isAssignableFrom(args[i])) 
        { 
         argumentsMatch = false; 
         break; 
        } 
       } 

       if(argumentsMatch) 
       { 
        //We found a matching constructor, return it 
        return constructor; 
       } 
      } 
     } 

     //No matching constructor 
     return null; 
    } 

    @Test 
    public void testGetConstructorForArgs() 
    { 
     //There's no constructor in HashSet that takes a String as a parameter 
     Assert.assertNull(getConstructorForArgs(HashSet.class, new Class[]{String.class})); 

     //There is a parameterless constructor in HashSet 
     Assert.assertNotNull(getConstructorForArgs(HashSet.class, new Class[]{})); 

     //There is a constructor in HashSet that takes int as parameter 
     Assert.assertNotNull(getConstructorForArgs(HashSet.class, new Class[]{int.class})); 

     //There is a constructor in HashSet that takes a Collection as it's parameter, test with Collection-interface 
     Assert.assertNotNull(getConstructorForArgs(HashSet.class, new Class[]{Collection.class})); 

     //There is a constructor in HashSet that takes a Collection as it's parameter, and HashSet itself is a Collection-implementation 
     Assert.assertNotNull(getConstructorForArgs(HashSet.class, new Class[]{HashSet.class})); 

     //There's no constructor in HashSet that takes an Object as a parameter 
     Assert.assertNull(getConstructorForArgs(HashSet.class, new Class[]{Object.class})); 

     //There is a constructor in HashSet that takes an int as first parameter and float as second 
     Assert.assertNotNull(getConstructorForArgs(HashSet.class, new Class[]{int.class, float.class})); 

     //There's no constructor in HashSet that takes an float as first parameter and int as second 
     Assert.assertNull(getConstructorForArgs(HashSet.class, new Class[]{float.class, int.class})); 
    } 
} 

Editar: Tenga en cuenta que esta solución no es ideal para todos los casos: si hay dos constructores, que tienen un parámetro que se puede asignar a un tipo de parámetro dado, será elegido el primero, incluso si el segundo fue un mejor ajuste. Por ejemplo, si SomeClass tuviera un constructor que tomara HashSet (A Collection -implementation) como parámetro, y un constructor tomando Collection como parámetro, el método podría devolver cualquiera de los dos cuando busque un constructor que acepte un parámetro HashSet, dependiendo de cuál vino primero al iterar a través de las clases. Si necesita funcionar también para esos casos, primero debe reunir todos los candidatos posibles, que coincidan con isAssignableFrom, y luego hacer un análisis más profundo para que los candidatos elijan el más adecuado.

+0

Agradable. Pero estoy buscando algo que pueda usarse fácilmente en 'clojure'. Por lo tanto, preferiría tener algo listo para usar y no tener que agregar funciones definidas por el usuario. Actualizó la pregunta. – viebel

+1

@YehonathanSharvit No estoy familiarizado con clojure, pero el método en sí puede moverse a la clase que elijas y declarar estático, así que si al menos puedes llamar a métodos estáticos de clojure, esto debería funcionar (ver las ediciones sobre posibles trampas) al final de mi respuesta, sin embargo). – esaj

+0

¿Entiende por qué esta función no es compatible con la infraestructura de reflejo? – viebel

0

Creo que puede obtener la clase principal y una lista de todas las interfaces implementadas -> para que pueda verificar primero el constructor de Hashset. Si no se encuentra nada, puede hacerlo de forma recursiva para todas las clases e interfaces principales hasta que encuentre alguna coincidente.

Cuestiones relacionadas