java class<t> - Cómo obtener una instancia de clase de genéricos tipo T




objeto clases (15)

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 ?


Answers

Imagina que tienes 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.class en la clase Foo seleccionando el Type (de la respuesta de bert bruynooghe) e inferiéndolo usando la instancia de la Class :

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 la ideal, por lo que es una buena idea almacenar en caché el valor computado para evitar cálculos múltiples sobre esto. 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.


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

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. Debe extenderse con la clase escrita ( Child extends Generic<Integer> )

O

  1. Debe crearse como implementación anónima ( new Generic<Integer>() {}; )

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

En realidad, supongo que tiene un campo en su clase de tipo T. Si no hay un campo de tipo T, ¿qué sentido tiene tener un Tipo genérico? Por lo tanto, simplemente puede hacer una instancia de ese campo.

En mi caso, tengo un

List<T> items;
en mi clase, y verifico 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.


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

Una solución popular para esto es pasar la Class del parámetro de tipo al constructor del tipo genérico, por ejemplo

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


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 más tarde en la clase derivada:

class Bar extends Foo<Whatever> {
    @Override
    Class<T> getTClass() {
        return Whatever.class;
    }
}

Estoy usando una solución para esto:

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

MyClass myClassInstance = MyClass.class.newInstance();

Un enfoque / solución / solución estándar es agregar un objeto de class al (a los) 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();
    }
 }

Estaba buscando una manera de hacerlo yo mismo sin agregar una dependencia adicional a la ruta de clase. Después de algunas investigaciones, descubrí que es posible siempre y cuando tengas un supertipo genérico. Esto estaba bien para mí, ya que estaba trabajando con una capa DAO con un supertipo de capa genérico. Si esto se adapta 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 Java pura

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

Solución de resorte

Mi proyecto estaba usando Spring que es aún mejor, ya que Spring tiene un método útil 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 ";
    }

Si está extendiendo o implementando cualquier clase / interfaz que esté usando genéricos, puede obtener el Tipo genérico de clase / interfaz padre, sin modificar ninguna clase / interfaz existente.

Podría haber tres posibilidades,

Caso 1 Cuando su 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á 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 usa 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> {
}

Como se explica en otras respuestas, para usar este enfoque de tipo de ParameterizedType , necesita extender la clase, pero parece ser un trabajo extra para crear una clase completamente nueva que lo extienda ...

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

@Getter
public abstract class ConfigurationDefinition<T> {

    private Class<T> type;
    ...

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

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

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


Basándose en la misma idea que los Super Type Tokens, podría crear una identificación escrita para usar en lugar de una cadena:

public abstract class TypedID<T extends Animal> {
  public final Type type;
  public final String id;

  protected TypedID(String id) {
    this.id = id;
    Type superclass = getClass().getGenericSuperclass();
    if (superclass instanceof Class) {
      throw new RuntimeException("Missing type parameter.");
    }
    this.type = ((ParameterizedType) superclass).getActualTypeArguments()[0];
  }
}

Pero creo que esto puede anular el propósito, ya que ahora necesita crear nuevos objetos de identificación para cada cadena y mantenerlos (o reconstruirlos con la información de tipo correcta).

Mouse jerry = new Mouse();
TypedID<Dog> spike = new TypedID<Dog>("spike") {};
TypedID<Duck> quacker = new TypedID<Duck>("quacker") {};

jerry.addFriend(spike, new Dog());
jerry.addFriend(quacker, new Duck());

Pero ahora puede usar la clase de la forma que quería originalmente, sin los lanzamientos.

jerry.callFriend(spike).bark();
jerry.callFriend(quacker).quack();

Esto es solo ocultar el parámetro type dentro del id, aunque sí significa que puede recuperar el tipo del identificador más adelante si lo desea.

Necesitará implementar los métodos de comparación y hashing de TypedID también si desea poder comparar dos instancias idénticas de una identificación.







java generics