2010-12-23 9 views
7

Tengo una interfaz parametrizada que se implementa de muchas maneras diferentes. En tiempo de ejecución necesito descubrir, dado un objeto arbitrario que implementa esa interfaz, cuáles son los parámetros de tipo reales para la interfaz.¿Cómo obtener los argumentos de tipo reales para una interfaz genérica implementada indirectamente?

He aquí un fragmento de ilustrar el problema, y ​​un intento a mitad de camino para resolverlo (also on ideone.com):

import java.util.*; 
import java.lang.reflect.*; 

interface Awesome<X> { } 
class Base<E> implements Awesome<Set<E>> { } 
class Child extends Base<List<Integer>> { } 

class AwesomeExample {  
    public static void main(String[] args) { 
     Awesome<Set<List<Integer>>> x = new Child(); 

     System.out.println(
      ((ParameterizedType) 
       Child.class.getGenericSuperclass() 
      ).getActualTypeArguments()[0] 
     ); 
     // prints "java.util.List<java.lang.Integer>" 

     System.out.println(
      ((ParameterizedType) 
       Base.class.getGenericInterfaces()[0] 
      ).getActualTypeArguments()[0] 
     ); 
     // prints "java.util.Set<E>"   

     investigate(x); 
     // we want this to print "Set<List<Integer>>" 
    } 

    static void investigate(Awesome<?> somethingAwesome) { 
     // how to do this? 
    } 
} 

Parece que hay suficiente información de tipo genérico en tiempo de ejecución para deducir que:

  • Child extends Base<List<Integer>>
  • Base<E> implements Awesome<Set<E>>

Y por lo tanto, podemos poner todas las partes y piezas para concluir que:

  • Child implements Awesome<Set<List<Integer>>>

lo que parece que el problema tiene solución, pero no es tan sencillo, ya que tendríamos para trabajar con una jerarquía de clase/interfaz arbitraria. ¿Es esta la única manera de hacer esto? ¿Hay alguna forma más simple? ¿Alguien ha escrito una biblioteca para hacer esto ya?

+0

No conozco una biblioteca que implemente esto. Lo hice yo mismo, y no fue muy agradable. Al menos, podría encontrar una implementación de token de tipo super: http://code.google.com/p/google-gson/source/browse/trunk/src/main/java/com/google/gson/reflect/TypeToken.java ? r = 60 y spec = svn89 –

Respuesta

-1

La respuesta corta es NO. Estoy de acuerdo que es una lástima ... :( La razón es que Java cae parámetros de tipo en la etapa de compilación. Ellos no existen en el código de bytes. Se utilizan solamente compilador.

para resolver su problema hay que añadir otro parámetro "normal" de tipo Class y pasarlo al constructor cuando se crea una instancia de la base:

class Base<E> implements Awesome<Set<E>> { 
    private E type; 
    public Base(E type) { 
     this.type = type; 
    } 
} 
+2

La información de tipo está en el código de bytes. Entonces no estás en lo correcto. Puede obtener la información del tipo en esta posición. Google para tokens súper tipo para obtener un ejemplo. –

+1

@Thomas: la información de tipo estático se almacena, la información dinámica no. – OrangeDog

4

Editar: es posible que sólo desee ver en el uso de: http://code.google.com/p/gentyref/

Si puede garantizar que todas las implementaciones de Awesome<?> no tendrán argumentos de tipo, la siguiente co de servirán para iniciar [1]:

static void investigate(Object o) { 
    final Class<?> c = o.getClass(); 
    System.out.println("\n" + c.getName() + " implements: "); 
    investigate(c, (Type[])null); 
} 

static void investigate(Type t, Type...typeArgs) { 
    if(t == null) return; 

    if(t instanceof Class<?>) { 
     investigate((Class<?>)t, typeArgs); 
    } else if(t instanceof ParameterizedType) { 
     investigate((ParameterizedType)t, typeArgs); 
    } 
} 

static void investigate(Class<?> c, Type...typeArgs) { 
    investigate(c.getGenericSuperclass(), typeArgs); 

    for(Type i : c.getGenericInterfaces()) { 
     investigate(i, typeArgs); 
    } 
} 

static void investigate(ParameterizedType p, Type...typeArgs) { 
    final Class<?> c = (Class<?>)p.getRawType(); 
    final StringBuilder b = new StringBuilder(c.getName()); 
    b.append('<'); 
    Type[] localArgs = p.getActualTypeArguments(); 
    if(typeArgs != null && typeArgs.length > 0) { 
     int i = 0, nextTypeArg = 0; 
     for(Type local : localArgs) { 
      if(local instanceof ParameterizedType) { 
       ParameterizedType localP = (ParameterizedType) local; 
       b.append(localP.getRawType()).append('<'); 
       b.append(typeArgs[nextTypeArg++]); 
       b.append('>'); 
      } else if(local instanceof TypeVariable) { 
       // reify local type arg to instantiated one. 
       localArgs[nextTypeArg] = typeArgs[nextTypeArg]; 
       b.append(localArgs[nextTypeArg]); 
       nextTypeArg++; 
      } else { 
       b.append(local.toString()); 
      } 
      b.append(", "); 
      i++; 
     } 
     if(typeArgs.length > 0) { 
      b.delete(b.length() - 2, b.length()); 
     } 
     b.append('>'); 
    } else { 
     String args = Arrays.toString(localArgs); 
     b.append(args.substring(1, args.length()-1)).append('>'); 
    } 
    System.out.println(b); 
    investigate(c, localArgs); 
} 

Si, sin embargo, se harán ejemplificaciones de Awesome<?> o Base<E>, la información de tipos se perderán debido al borrado. Esto se puede evitar, como una convención, con algo como esto:

Awesome<?> awesome = new Base<Double>() {}; 

Aviso del {}, esto crea una nueva clase anónima que implementa (o se extiende aquí) Base<E>. Esta clase tendrá sus parámetros de tipo disponibles para la reflexión.

Si tienes miedo de hacer cumplir esta convención será un problema, puede ocultar los constructores & sólo se exponen métodos de fábrica:

class Base<E> implements Awesome<Set<E>> { 

    public static Base<Number> newNumberInstance() { 
     return new Base<Number>() {}; 
    } 

    protected Base() {} 
} 

Como el código anterior no ha sido completamente probado, es posible que desee hacer ese. El punto aquí es que puede encontrar los parámetros de tipo reales dado que sus requisitos son lo suficientemente estrictos. Si eso se aplica o no a su situación depende de usted para determinar.

[1] Imprimirá todas las interfaces una clase implementa & no solo los parámetros de tipo Awesome. Esto se puede cambiar, pero pensé que iría para obtener información más general sobre &, que le permite resolver los detalles. Por ejemplo, tendrá que poner a prueba estos para ver lo que quiero decir:

investigate(new ArrayList<Integer>()); 
investigate(new ArrayList<String>() {}); // new anonymous ArrayList class 
investigate(""); 
investigate(new Awesome<Comparable<?>>() {}); // new anonymous implementation of Awesome 
0

Tras la propuesta de @ oconnor0, aquí es cómo hacerlo con gentyref:

static Type investigate(Awesome<?> somethingAwesome) { 
    return GenericTypeReflector.getTypeParameter(somethingAwesome.getClass(), Awesome.class.getTypeParameters()[0]); 
} 

En caso podría ser somethingAwesome.getClass() también genérico, podría ser útil pasarlo primero a través del GenericTypeReflector.addWildcardParameters.

Spring también tiene un GenericTypeResolver.resolveTypeArgument(Class,Class) que puede lograr el mismo resultado.

Cuestiones relacionadas