2011-06-15 11 views
7

que tienen una clase Foo<T, U> con el siguiente constructor:Obtención de argumentos de tipo de clase parametrizada

public Foo() { 
    clazz = Class<U>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[1]; 
} 

Lo que hago en el constructor está consiguiendo la clase del argumento U. Lo necesito porque lo uso para crear una instancia de esa clase.

El problema es que no funciona cuando tengo una subclase de Foo que no es una subcategoría directa de la misma. Déjame ponerlo con un ejemplo.

Tengo la clase Bar<T> extends Foo<T, Class1>. Aquí, Class1 no es una variable sino una clase.

También tengo la clase Baz extends Bar<Class2>. Class2 es una clase también, no una variable.

El problema es que falla al intentar crear una instancia de Baz (Baz -> Bar<Class2> -> Foo<T, Class2>). Obtengo ArrayIndexOutOfBoundsException porque getActualTypeArguments() devuelve una matriz que contiene solo la clase Class2 (tamaño 1) y estoy tratando de obtener el segundo elemento de la matriz. Eso es porque obtengo los argumentos de Bar<Class2>, en lugar de los de Foo.

Lo que quiero es modificar el constructor de Foo de alguna manera que pueda obtener la clase en el parámetro U, no importa si la instancia de clase I es una subclase directa o no. Creo que debería poder subir a la jerarquía de clases hasta llegar a la clase Foo, convertirla en ParameterizedType y obtener los argumentos, pero no pude encontrar cómo.

¿Alguna idea?

Gracias de antemano.

Respuesta

3

Este es un enfoque agradable: es la única manera de obtener realmente la información de tipo genérico compilada dentro del bytecode de Java. Todo lo demás en términos de genéricos en Java es simplemente borrar tipo. Vea a continuación lo que parece ser una solución de trabajo. Hay cuatro escenarios:

  1. Un nivel de herencia bastante complejo
  2. Su escenario herencia
  3. La clase en sí (clazz habrá null)
  4. Una subclase que no proporciona valores reales (clazz = null)

Este es el código (Test.java):

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

class A<T1, T2> 
{ 
    Class<T2> clazz; 

    A() 
    { 
    Type sc = getClass().getGenericSuperclass(); 
    Map<TypeVariable<?>, Class<?>> map = new HashMap<TypeVariable<?>, Class<?>>(); 
    while (sc != null) 
    { 
     if (sc instanceof ParameterizedType) 
     { 
     ParameterizedType pt = (ParameterizedType) sc; 
     Type[] ata = pt.getActualTypeArguments(); 
     TypeVariable[] tps = ((Class) pt.getRawType()) 
      .getTypeParameters(); 
     for (int i = 0; i < tps.length; i++) 
     { 
      Class<?> value; 
      if (ata[i] instanceof TypeVariable) 
      { 
      value = map.get(ata[i]); 
      } 
      else 
      { 
      value = (Class) ata[i]; 
      } 
      map.put(tps[i], value); 
     } 
     if (pt.getRawType() == A.class) 
     { 
      break; 
     } 
     if (ata.length >= 1) 
     { 
      sc = ((Class) pt.getRawType()).getGenericSuperclass(); 
     } 
     } 
     else 
     { 
     sc = ((Class) sc).getGenericSuperclass(); 
     } 
    } 

    TypeVariable<?> myVar = A.class.getTypeParameters()[1]; 
    clazz = map.containsKey(myVar) ? (Class<T2>) map.get(myVar) : null; 
    } 
} 

class Bar<T> extends A<T, String> {} 
class Baz extends Bar<Integer> {} 

class A2<T3, T1, T2> extends A<T1, T2> { } 
class B<T> extends A2<Long, String, T> { } 
class C extends B<Integer> { } 
class D extends C { } 

class Plain<T1, T2> extends A<T1, T2> {} 

public class Test 
{ 
    public static void main(String[] args) 
    { 
    new D(); 
    new Baz(); 
    new A<String, Integer>(); 
    new Plain<String, Integer>(); 
    } 
} 
3

Creé un método que le devolverá una variedad de tipos que parametrizaron el genérico donde 'null' indica los parámetros que aún existen. Lo probé contra su jerarquía de Foo, así como también el ejemplo provisto en la otra respuesta que usa la clase 'A'.

Editar: Antes no había considerado cómo el reordenamiento de los tipos afectaría el enlace. En el nuevo método, pase la superclase de la que desea los parámetros y la clase base. A medida que avanzo hacia la superclase, recopilo los parámetros de tipo que son clases y trazo hacia delante desde la clase base anterior comparando los nombres de los parámetros. Espero que eso sea lo suficientemente claro.

public <S, B extends S> Class[] findTypeParameters(Class<B> base, Class<S> superClass) { 
    Class[] actuals = new Class[0]; 
    for (Class clazz = base; !clazz.equals(superClass); clazz = clazz.getSuperclass()) { 
     if (!(clazz.getGenericSuperclass() instanceof ParameterizedType)) 
      continue; 

     Type[] types = ((ParameterizedType) clazz.getGenericSuperclass()).getActualTypeArguments(); 
     Class[] nextActuals = new Class[types.length]; 
     for (int i = 0; i < types.length; i++) 
      if (types[i] instanceof Class) 
       nextActuals[i] = (Class) types[i]; 
      else 
       nextActuals[i] = map(clazz.getTypeParameters(), types[i], actuals); 
     actuals = nextActuals; 
    } 
    return actuals; 
} 

private Class map(Object[] variables, Object variable, Class[] actuals) { 
    for (int i = 0; i < variables.length && i < actuals.length; i++) 
     if (variables[i].equals(variable)) 
      return actuals[i]; 
    return null; 
} 

@Test 
public void findsTypeOfSuperclass() throws Exception { 
    assertThat(findTypeParameters(A4.class, A1.class), arrayContaining(Integer.class, Boolean.class, String.class)); 
    assertThat(findTypeParameters(B4.class, A1.class), arrayContaining(Integer.class, Boolean.class, String.class)); 
    assertThat(findTypeParameters(C2.class, A1.class), arrayContaining(Integer.class, null, null)); 
    assertThat(findTypeParameters(D4.class, A1.class), arrayContaining(Integer.class, null, String.class)); 
} 

class A1<T1, T2, T3> {} 
class A2<T3, T2> extends A1<Integer, T2, T3> {} 
class A3<T2> extends A2<String, T2> {} 
class A4 extends A3<Boolean> {} 

class B2<X, T3, Y, T2, Z> extends A1<Integer, T2, T3> {} 
class B3<X, T2, Y, Z> extends B2<X, String, Y, T2, Z> {} 
class B4<X> extends B3<X, Boolean, X, X> {} 

class C2<T2, T3> extends A1<Integer, T2, T3> {} 

class D2<T2> extends A1<Integer, T2, String> {} 
class D3 extends D2 {} 
class D4 extends D3 {} 
+1

Gracias por la respuesta :). Funciona para mi Una cosa que me di cuenta es que no se tuvo en cuenta el caso cuando se cambia el orden de los parámetros, por ejemplo, 'Bar se extiende Foo '. Cuando coloque la clase de argumento en la primera posición vacía en la matriz, en este ejemplo, obtendrá los tipos incorrectos. ¿No lo harás? – drumkey

Cuestiones relacionadas