¿Cómo obtengo una instancia de clase de tipo T genérico?

700

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

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

robinmag
fuente
2
Pruebe las respuestas en esta pregunta. Creo que es similar. stackoverflow.com/questions/1942644/…
Emil
1
posible duplicado de instanciar una clase genérica en Java
matiasg
1
import com.fasterxml.jackson.core.type.TypeReference; new TypeReference<T>(){}
Bogdan Shulga

Respuestas:

569

La respuesta corta es que no hay forma de averiguar el tipo de tiempo de ejecución de los parámetros de tipo genérico en Java. Sugiero leer el capítulo sobre borrado de tipos en el Tutorial de Java para obtener más detalles.

Una solución popular para esto es pasar el Classparámetro del tipo al constructor del tipo genérico, p. Ej.

class Foo<T> {
    final Class<T> typeParameterClass;

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

    public void bar() {
        // you can access the typeParameterClass here and do whatever you like
    }
}
Zsolt Török
fuente
73
Esta respuesta proporciona una solución válida, pero es incorrecto decir que no hay forma de encontrar el tipo genérico en tiempo de ejecución. Resulta que el borrado tipo es mucho más complejo que el borrado general. Mi respuesta muestra cómo obtener las clases de tipo genérico.
Ben Thurley
3
Truco de @BenThurley Neat, pero por lo que puedo ver, solo funciona si hay un supertipo genérico para que lo use. En mi ejemplo, no puede recuperar el tipo de T en Foo <T>.
Zsolt Török
@webjockey No, no deberías. Asignar typeParameterClasssin una asignación predeterminada en el constructor está perfectamente bien. No es necesario configurarlo por segunda vez.
Adowrath
Esta es la primera solución que viene a la mente, pero a veces no eres tú quien creará / iniciará objetos. Entonces no podrás usar el constructor. Por ejemplo, al recuperar entidades JPA de la base de datos.
Paramvir Singh Karwal
233

Estaba buscando una manera de hacerlo yo mismo sin agregar una dependencia adicional al classpath. Después de investigar un poco, descubrí que es posible siempre que tenga un supertipo genérico. Esto estuvo bien para mí ya que estaba trabajando con una capa DAO con un supertipo de capa genérica. Si esto se ajusta a su escenario, entonces es el enfoque más perfecto 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 pura de Java

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

@SuppressWarnings("unchecked")
public GenericJpaDao() {
  this.entityBeanType = ((Class) ((ParameterizedType) getClass()
      .getGenericSuperclass()).getActualTypeArguments()[0]);
}

Solución de primavera

Mi proyecto estaba usando Spring, que es aún mejor, ya que Spring tiene un útil método de utilidad para encontrar el tipo. Este es el mejor enfoque para mí, ya que se ve más limpio. 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 ";
    }
Ben Thurley
fuente
1
Por favor, aclarar el significado de resolveTypeArgument argumentos
gstackoverflow
getClass () es un método de java.lang.Object que devolverá la clase del objeto específico en tiempo de ejecución, este es el objeto para el que desea resolver el tipo. AbstractHibernateDao.class es solo el nombre de la clase base o superclase de la jerarquía de clases de tipo genérico. La declaración de importación está incluida, por lo que debería poder encontrar fácilmente los documentos y comprobarlo usted mismo. Esta es la página docs.spring.io/spring/docs/current/javadoc-api/org/…
Ben Thurley
¿Cuál es la solución de primavera con la versión 4.3.6 y superior? No funciona con la primavera 4.3.6.
Erlan
1
El enlace en "Solución Java pura" está roto, ahora es blog.xebia.com/acessing-generic-types-at-runtime-in-java
Nick Breen
1
@ AlikElzin-kilaka se inicializa en el constructor utilizando la clase Spring GenericTypeResolver.
Ben Thurley
103

Sin embargo, hay una pequeña laguna: si define su Fooclase como abstracta. Eso significaría que debe crear una instancia de su clase como:

Foo<MyType> myFoo = new Foo<MyType>(){};

(Tenga en cuenta las llaves dobles al final).

Ahora puede recuperar el tipo de Ten tiempo de ejecución:

Type mySuperclass = myFoo.getClass().getGenericSuperclass();
Type tType = ((ParameterizedType)mySuperclass).getActualTypeArguments()[0];

Sin embargo, mySuperclasstenga en cuenta que tiene que ser la superclase de la definición de clase que realmente define el tipo final paraT .

Tampoco es muy elegante, pero debe decidir si prefiere new Foo<MyType>(){}o new Foo<MyType>(MyType.class);en su código.


Por ejemplo:

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;

import java.util.ArrayDeque;
import java.util.Deque;
import java.util.NoSuchElementException;

/**
 * Captures and silently ignores stack exceptions upon popping.
 */
public abstract class SilentStack<E> extends ArrayDeque<E> {
  public E pop() {
    try {
      return super.pop();
    }
    catch( NoSuchElementException nsee ) {
      return create();
    }
  }

  public E create() {
    try {
      Type sooper = getClass().getGenericSuperclass();
      Type t = ((ParameterizedType)sooper).getActualTypeArguments()[ 0 ];

      return (E)(Class.forName( t.toString() ).newInstance());
    }
    catch( Exception e ) {
      return null;
    }
  }
}

Entonces:

public class Main {
    // Note the braces...
    private Deque<String> stack = new SilentStack<String>(){};

    public static void main( String args[] ) {
      // Returns a new instance of String.
      String s = stack.pop();
      System.out.printf( "s = '%s'\n", s );
    }
}
bert bruynooghe
fuente
55
¡Esta es fácilmente la mejor respuesta aquí! Además, por lo que vale, esta es la estrategia que utiliza Google Guice para vincular clases conTypeLiteral
ATG
14
Tenga en cuenta que cada vez que se utiliza este método de construcción de objetos, se crea una nueva clase anónima. En otras palabras, dos objetos ay bcreados de esta manera extenderán la misma clase pero no tendrán clases de instancia idénticas. a.getClass() != b.getClass()
Martin Serrano
3
Hay un escenario en el que esto no funciona. Si Foo implementara una interfaz, como Serializable, entonces la clase anónima no sería Serializable a menos que la instancia de la clase lo sea. Intenté solucionarlo creando una clase de fábrica serializable que crea la clase anónima derivada de Foo, pero luego, por alguna razón, getActualTypeArguments devuelve el tipo genérico en lugar de la clase real. Por ejemplo: (nuevo FooFactory <MyType> ()). CreateFoo ()
Lior Chaga
38

Un enfoque / solución / solución estándar es agregar un classobjeto a los constructores, 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();
    }
 }
Andreas Dolk
fuente
1
Pero parece que no se puede @autowired en el uso real, alguna forma de evitarlo?
Alfred Huang
@AlfredHuang La solución sería crear un bean para la clase que haga esto y no depender del cableado automático.
Calebj
20

Imagine que tiene una superclase abstracta que es genérica:

public abstract class Foo<? extends T> {}

Y luego tienes una segunda clase que extiende Foo con una barra genérica que extiende T:

public class Second extends Foo<Bar> {}

Puede obtener la clase Bar.classen la clase Foo seleccionando Type(de bert bruynooghe answer) e inferiéndola usando la Classinstancia:

Type mySuperclass = myFoo.getClass().getGenericSuperclass();
Type tType = ((ParameterizedType)mySuperclass).getActualTypeArguments()[0];
//Parse it as String
String className = tType.toString().split(" ")[1];
Class clazz = Class.forName(className);

Debe tener en cuenta que esta operación no es ideal, por lo que es una buena idea almacenar en caché el valor calculado para evitar múltiples cálculos al respecto. Uno de los usos típicos es en la implementación genérica de DAO.

La implementación final:

public abstract class Foo<T> {

    private Class<T> inferedClass;

    public Class<T> getGenericClass(){
        if(inferedClass == null){
            Type mySuperclass = getClass().getGenericSuperclass();
            Type tType = ((ParameterizedType)mySuperclass).getActualTypeArguments()[0];
            String className = tType.toString().split(" ")[1];
            inferedClass = Class.forName(className);
        }
        return inferedClass;
    }
}

El valor devuelto es Bar.class cuando se invoca desde la clase Foo en otra función o desde la clase Bar.

droidpl
fuente
1
toString().split(" ")[1]ese era el problema, evita el"class "
IgniteCoders
16

Aquí hay una 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: Solo se puede usar como superclase

  1. Tiene que extenderse con la clase escrita ( Child extends Generic<Integer>)

O

  1. Tiene que ser creado como implementación anónima ( new Generic<Integer>() {};)
Yaroslav Kovbas
fuente
3
getTypeName llama a toString, por lo que puede reemplazarse por .getActualTypeArguments () [0] .toString ();
Yaroslav Kovbas
9

Una ruta mejor que la Clase sugerida por los demás es pasar un objeto que pueda hacer lo que hubiera hecho con la Clase, por ejemplo, crear una nueva instancia.

interface Factory<T> {
  T apply();
}

<T> void List<T> make10(Factory<T> factory) {
  List<T> result = new ArrayList<T>();
  for (int a = 0; a < 10; a++)
    result.add(factory.apply());
  return result;
}

class FooFactory<T> implements Factory<Foo<T>> {
  public Foo<T> apply() {
    return new Foo<T>();
  }
}

List<Foo<Integer>> foos = make10(new FooFactory<Integer>());
Ricky Clarkson
fuente
@ Ricky Clarkson: No veo cómo esta fábrica debería devolver foos parametrizados. ¿Podría explicar cómo saca a Foo <T> de esto? Me parece que esto solo da Foo no parametrizado. ¿No es la T en make10 simplemente Foo aquí?
ib84
@ ib84 He arreglado el código; Parece que me perdí que Foo fue parametrizado cuando escribí la respuesta originalmente.
Ricky Clarkson
9

Tuve este problema en una clase genérica abstracta. En este caso particular, la solución es más simple:

abstract class Foo<T> {
    abstract Class<T> getTClass();
    //...
}

y luego en la clase derivada:

class Bar extends Foo<Whatever> {
    @Override
    Class<T> getTClass() {
        return Whatever.class;
    }
}
usuario1154664
fuente
Sí, lo es, pero me gustaría dejar un mínimo que debe hacerse al extender esta clase. Verifique la respuesta de
droidpl
5

Tengo una solución (fea pero efectiva) para este problema, que utilicé recientemente:

import java.lang.reflect.TypeVariable;


public static <T> Class<T> getGenericClass()
{
    __<T> ins = new __<T>();
    TypeVariable<?>[] cls = ins.getClass().getTypeParameters(); 

    return (Class<T>)cls[0].getClass();
}

private final class __<T> // generic helper class which does only provide type information
{
    private __()
    {
    }
}
desconocido6656
fuente
3

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

public class MyClass<A, B, C> {

}

Ahora creemos 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 debería 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];
    }
}
Juan urrego
fuente
2

Como se explica en otras respuestas, para usar esto ParameterizedType enfoque, necesita extender la clase, pero eso parece un trabajo extra para hacer una clase completamente nueva que la extienda ...

Entonces, hacer que la clase sea abstracta te obliga a extenderla, satisfaciendo así el requisito de subclasificación. (usando lombok's @Getter).

@Getter
public abstract class ConfigurationDefinition<T> {

    private Class<T> type;
    ...

    public ConfigurationDefinition(...) {
        this.type = (Class<T>) ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[0];
        ...
    }
}

Ahora para extenderlo sin definir una nueva clase. (Tenga en cuenta el {} al final ... extendido, pero no sobrescriba nada, a menos que lo desee).

private ConfigurationDefinition<String> myConfigA = new ConfigurationDefinition<String>(...){};
private ConfigurationDefinition<File> myConfigB = new ConfigurationDefinition<File>(...){};
...
Class stringType = myConfigA.getType();
Class fileType = myConfigB.getType();
Riaan Schutte
fuente
2

Supongo que, dado que tiene una clase genérica, tendría una variable como esa:

private T t;

(esta variable necesita tomar un valor en el constructor)

En ese caso, simplemente puede crear el siguiente método:

Class<T> getClassOfInstance()
{
    return (Class<T>) t.getClass();
}

¡Espero eso ayude!

Pontios
fuente
1
   public <T> T yourMethodSignature(Class<T> type) {

        // get some object and check the type match the given type
        Object result = ...            

        if (type.isAssignableFrom(result.getClass())) {
            return (T)result;
        } else {
            // handle the error
        }
   }
Abel ANEIROS
fuente
1

Si está ampliando o implementando una clase / interfaz que usa 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 su clase está extendiendo una clase que está utilizando 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á usando 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 está extendiendo una interfaz que está usando 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> {
}
Anil Agrawal
fuente
1

Eso es bastante sencillo. Si necesita desde dentro de la misma clase:

Class clazz = this.getClass();
ParameterizedType parameterizedType = (ParameterizedType) clazz.getGenericSuperclass();
try {
        Class typeClass = Class.forName( parameterizedType.getActualTypeArguments()[0].getTypeName() );
        // You have the instance of type 'T' in typeClass variable

        System.out.println( "Class instance name: "+  typeClass.getName() );
    } catch (ClassNotFoundException e) {
        System.out.println( "ClassNotFound!! Something wrong! "+ e.getMessage() );
    }
Gana
fuente
0

En realidad, supongo que tiene un campo en su 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 en ese campo.

En mi caso, tengo un

Listar <T> elementos;
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.

Christine
fuente
44
¿Qué hago si items.isEmpty () es verdadero?
chaotic3quilibrium
0

Esta pregunta es antigua, pero ahora lo mejor es usar google Gson.

Un ejemplo para personalizar viewModel.

Class<CustomViewModel<String>> clazz = new GenericClass<CustomViewModel<String>>().getRawType();
CustomViewModel<String> viewModel = viewModelProvider.get(clazz);

Clase de tipo genérico

class GenericClass<T>(private val rawType: Class<*>) {

    constructor():this(`$Gson$Types`.getRawType(object : TypeToken<T>() {}.getType()))

    fun getRawType(): Class<T> {
        return rawType as Class<T>
    }
}
Vahe Gharibyan
fuente
0

Quería pasar T.class a un método que utilizara genéricos

El método readFile lee un archivo .csv especificado por fileName con fullpath. Puede haber archivos csv con diferentes contenidos, por lo tanto, necesito pasar la clase de archivo de modelo para poder obtener los objetos apropiados. Como se trata de leer un archivo csv, quería hacerlo de forma genérica. Por alguna razón u otra, ninguna de las soluciones anteriores funcionó para mí. Necesito usar Class<? extends T> typepara que funcione. Yo uso la biblioteca opencsv para analizar los archivos CSV.

private <T>List<T> readFile(String fileName, Class<? extends T> type) {

    List<T> dataList = new ArrayList<T>();
    try {
        File file = new File(fileName);

        Reader reader = new BufferedReader(new InputStreamReader(new FileInputStream(file)));
        Reader headerReader = new BufferedReader(new InputStreamReader(new FileInputStream(file)));

        CSVReader csvReader = new CSVReader(headerReader);
        // create csv bean reader
        CsvToBean<T> csvToBean = new CsvToBeanBuilder(reader)
                .withType(type)
                .withIgnoreLeadingWhiteSpace(true)
                .build();

        dataList = csvToBean.parse();
    }
    catch (Exception ex) {
        logger.error("Error: ", ex);
    }

    return dataList;
}

Así es como se llama al método readFile

List<RigSurfaceCSV> rigSurfaceCSVDataList = readSurfaceFile(surfaceFileName, RigSurfaceCSV.class);
Madhu Tomy
fuente
-4

Estoy usando una solución para esto:

class MyClass extends Foo<T> {
....
}

MyClass myClassInstance = MyClass.class.newInstance();
nikolai.serdiuk
fuente