Pregunta Cómo obtener una instancia de clase de genéricos tipo T


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 T.class.

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


513
2017-08-09 06:58


origen


Respuestas:


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 tipo en el Tutorial de Java para más detalles.

Una solución popular para esto es pasar el Class del parámetro de tipo en el constructor del tipo genérico, p.

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
    }
}

440
2017-08-09 07:03



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 un DAO capa 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, p. List<T> para ArrayList<T> o GenericDAO<T> para DAO<T>, etc.

Solución Pure Java

El artículo Accediendo a tipos genéricos en tiempo de ejecución en Java explica cómo puedes hacerlo usando Java puro.

Solución de primavera

Mi proyecto estaba usando Primavera 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 ";
    }

179
2018-02-08 22:14



Sin embargo, hay una pequeña laguna: si defines tu Foo clase como abstracto Eso significaría que tienes que instanciar tu clase como:

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

(Tenga en cuenta las llaves dobles al final.)

Ahora puedes recuperar el tipo de T en tiempo de ejecución:

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

Tenga en cuenta sin embargo que mySuperclass tiene que ser la superclase de la definición de clase que realmente define el tipo final para T.

Tampoco es muy elegante, pero debes decidir si prefieres new Foo<MyType>(){} o new Foo<MyType>(MyType.class); en tu 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 );
    }
}

78
2018-04-16 05:51



Un enfoque / solución alternativa / solución estándar es agregar un class objetar 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();
    }
 }

33
2017-08-09 07:07



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 se extiende T:

public class Second extends Foo<Bar> {}

Puedes obtener la clase Bar.class en la clase Foo seleccionando el Type (de la respuesta de bert bruynooghe) e inferirlo usando Class ejemplo:

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 cálculos múltiples en este. Uno de los usos típicos es 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.


17
2018-03-26 23:20



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: Puede ser usado solo como superclase

  1. Tiene que ser extendido con clase tipeada (Child extends Generic<Integer>)

O

  1. Tiene que ser creado como una implementación anónima (new Generic<Integer>() {};)

10
2017-11-30 11:09



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;
    }
}

8
2017-10-21 02:41



No puede hacerlo debido a la borradura de tipo. Véase también Pregunta de desbordamiento de pila Genéricos de Java - borrado de tipo - cuándo y qué sucede.


8
2017-08-09 07:02



Una mejor ruta que la Clase que otros sugirieron es pasar un objeto que pueda hacer lo que usted 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>());

7
2017-08-09 11:47