[java] Cómo obtener una instancia de clase de genéricos tipo T


Answers

Estaba buscando una forma de hacerlo yo mismo sin agregar una dependencia adicional al classpath. Después de investigar, descubrí que es posible siempre que tengas un supertipo genérico. Esto estuvo bien para mí ya que estaba trabajando con una capa DAO con un supertipo de capa genérico. Si esto se ajusta a su escenario, entonces es el mejor enfoque en mi humilde opinión.

La mayoría de los casos de uso de genéricos que he encontrado tienen algún tipo de supertipo genérico, por ejemplo, List<T> para ArrayList<T> o GenericDAO<T> para DAO<T> , etc.

Solución Pure Java

El artículo Acceder a los tipos genéricos en tiempo de ejecución en Java explica cómo puede hacerlo utilizando Java puro.

Solución de primavera

Mi proyecto estaba usando Spring que es aún mejor ya que Spring tiene un práctico método de utilidad para encontrar el tipo. Este es el mejor enfoque para mí, ya que se ve mejor. Supongo que si no estuvieras usando Spring, podrías escribir tu propio método de utilidad.

import org.springframework.core.GenericTypeResolver;

public abstract class AbstractHibernateDao<T extends DomainObject> implements DataAccessObject<T>
{

    @Autowired
    private SessionFactory sessionFactory;

    private final Class<T> genericType;

    private final String RECORD_COUNT_HQL;
    private final String FIND_ALL_HQL;

    @SuppressWarnings("unchecked")
    public AbstractHibernateDao()
    {
        this.genericType = (Class<T>) GenericTypeResolver.resolveTypeArgument(getClass(), AbstractHibernateDao.class);
        this.RECORD_COUNT_HQL = "select count(*) from " + this.genericType.getName();
        this.FIND_ALL_HQL = "from " + this.genericType.getName() + " t ";
    }
Question

Tengo una clase de genéricos, Foo<T> . En un método de Foo , quiero obtener la instancia de clase de tipo T, pero simplemente no puedo llamar a T.class .

¿Cuál es la forma preferida de T.class usando T.class ?




Encontré una manera genérica y simple de hacer eso. En mi clase, creé un método que devuelve el tipo genérico de acuerdo con su posición en la definición de la clase. Supongamos una definición de clase como esta:

public class MyClass<A, B, C> {

}

Ahora vamos a crear algunos atributos para persistir los tipos:

public class MyClass<A, B, C> {

    private Class<A> aType;

    private Class<B> bType;

    private Class<C> cType;

// Getters and setters (not necessary if you are going to use them internally)

    } 

Luego puede crear un método genérico que devuelva el tipo basado en el índice de la definición genérica:

   /**
     * Returns a {@link Type} object to identify generic types
     * @return type
     */
    private Type getGenericClassType(int index) {
        // To make it use generics without supplying the class type
        Type type = getClass().getGenericSuperclass();

        while (!(type instanceof ParameterizedType)) {
            if (type instanceof ParameterizedType) {
                type = ((Class<?>) ((ParameterizedType) type).getRawType()).getGenericSuperclass();
            } else {
                type = ((Class<?>) type).getGenericSuperclass();
            }
        }

        return ((ParameterizedType) type).getActualTypeArguments()[index];
    }

Finalmente, en el constructor simplemente llame al método y envíe el índice para cada tipo. El código completo debe verse así:

public class MyClass<A, B, C> {

    private Class<A> aType;

    private Class<B> bType;

    private Class<C> cType;


    public MyClass() {
      this.aType = (Class<A>) getGenericClassType(0);
      this.bType = (Class<B>) getGenericClassType(1);
      this.cType = (Class<C>) getGenericClassType(2);
    }

   /**
     * Returns a {@link Type} object to identify generic types
     * @return type
     */
    private Type getGenericClassType(int index) {

        Type type = getClass().getGenericSuperclass();

        while (!(type instanceof ParameterizedType)) {
            if (type instanceof ParameterizedType) {
                type = ((Class<?>) ((ParameterizedType) type).getRawType()).getGenericSuperclass();
            } else {
                type = ((Class<?>) type).getGenericSuperclass();
            }
        }

        return ((ParameterizedType) type).getActualTypeArguments()[index];
    }
}



En realidad, supongo que tienes un campo en tu clase de tipo T. Si no hay un campo de tipo T, ¿de qué sirve tener un tipo genérico? Entonces, simplemente puede hacer una instancia de ese campo.

En mi caso, tengo un

List<T> items;
en mi clase, y compruebo si el tipo de clase es "Localidad" por

if (items.get(0) instanceof Locality) ...

Por supuesto, esto solo funciona si el número total de clases posibles es limitado.







Un enfoque / solución alternativa / solución estándar es agregar un objeto de class al constructor (es), como:

 public class Foo<T> {

    private Class<T> type;
    public Foo(Class<T> type) {
      this.type = type;
    }

    public Class<T> getType() {
      return type;
    }

    public T newInstance() {
      return type.newInstance();
    }
 }



Aquí está la solución de trabajo!

@SuppressWarnings("unchecked")
    private Class<T> getGenericTypeClass() {
        try {
            String className = ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0].getTypeName();
            Class<?> clazz = Class.forName(className);
            return (Class<T>) clazz;
        } catch (Exception e) {
            throw new IllegalStateException("Class is not parametrized with generic type!!! Please use extends <> ");
        }
    } 

NOTAS: Se puede usar solo como superclase
1. Tiene que ser extendido con clase tipeada ( Child extends Generic<Integer> )
O
2. Tiene que crearse como implementación anónima ( new Generic<Integer>() {}; )




Si está ampliando o implementando cualquier clase / interfaz que utiliza genéricos, puede obtener el tipo genérico de clase / interfaz principal, sin modificar ninguna clase / interfaz existente.

Podría haber tres posibilidades,

Caso 1 Cuando tu clase está extendiendo una clase que está usando genéricos

public class TestGenerics {
    public static void main(String[] args) {
        Type type = TestMySuperGenericType.class.getGenericSuperclass();
        Type[] gTypes = ((ParameterizedType)type).getActualTypeArguments();
        for(Type gType : gTypes){
            System.out.println("Generic type:"+gType.toString());
        }
    }
}

class GenericClass<T> {
    public void print(T obj){};
}

class TestMySuperGenericType extends GenericClass<Integer> {
}

Caso 2 Cuando su clase está implementando una interfaz que está utilizando genéricos

public class TestGenerics {
    public static void main(String[] args) {
        Type[] interfaces = TestMySuperGenericType.class.getGenericInterfaces();
        for(Type type : interfaces){
            Type[] gTypes = ((ParameterizedType)type).getActualTypeArguments();
            for(Type gType : gTypes){
                System.out.println("Generic type:"+gType.toString());
            }
        }
    }
}

interface GenericClass<T> {
    public void print(T obj);
}

class TestMySuperGenericType implements GenericClass<Integer> {
    public void print(Integer obj){}
}

Caso 3 Cuando su interfaz amplía una interfaz que utiliza genéricos

public class TestGenerics {
    public static void main(String[] args) {
        Type[] interfaces = TestMySuperGenericType.class.getGenericInterfaces();
        for(Type type : interfaces){
            Type[] gTypes = ((ParameterizedType)type).getActualTypeArguments();
            for(Type gType : gTypes){
                System.out.println("Generic type:"+gType.toString());
            }
        }
    }
}

interface GenericClass<T> {
    public void print(T obj);
}

interface TestMySuperGenericType extends GenericClass<Integer> {
}








Related