Como obtenho uma instância de classe do tipo genérico T?

700

Eu tenho uma aula de genéricos Foo<T>. Em um método de Foo, eu quero obter a instância de classe do tipo T, mas simplesmente não consigo chamar T.class.

Qual é a maneira preferida de contornar isso usando T.class?

robinmag
fonte
2
Tente respostas nesta pergunta. Eu acho que é semelhante. stackoverflow.com/questions/1942644/…
Emil
1
possível duplicata de instanciar uma classe genérica em Java
matiasg
1
import com.fasterxml.jackson.core.type.TypeReference; new TypeReference<T>(){}
Bogdan Shulga

Respostas:

567

A resposta curta é que não há como descobrir o tipo de tempo de execução dos parâmetros de tipo genérico em Java. Sugiro a leitura do capítulo sobre apagamento de tipo no Tutorial Java para obter mais detalhes.

Uma solução popular para isso é passar o Classparâmetro do tipo para o construtor do tipo genérico, por exemplo,

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
fonte
73
Esta resposta fornece uma solução válida, mas é impreciso dizer que não há como encontrar o tipo genérico em tempo de execução. Acontece que o apagamento de tipo é muito mais complexo do que um apagamento geral. Minha resposta mostra como obter o tipo genérico de classes.
Ben Thurley
3
@BenThurley Neat truque, mas até onde eu posso ver, só funciona se houver um supertipo genérico para ele usar. No meu exemplo, você não pode recuperar o tipo de T em Foo <T>.
Zsolt Török
@webjockey Não, você não deveria. Atribuir o typeParameterClasssem uma atribuição padrão no construtor é perfeitamente adequado. Não há necessidade de configurá-lo uma segunda vez.
Adowrath
Esta é a primeira solução que vem à mente, mas às vezes não é você quem criará / iniciará objetos. Portanto, você não poderá usar o construtor. Por exemplo, ao recuperar entidades JPA do banco de dados.
Paramvir Singh Karwal
233

Eu estava procurando uma maneira de fazer isso sozinho sem adicionar uma dependência extra ao caminho da classe. Depois de alguma investigação, descobri que é possível desde que você tenha um supertipo genérico. Isso foi bom para mim, pois estava trabalhando com uma camada DAO com um supertipo de camada genérica. Se isso se encaixa no seu cenário, é a melhor abordagem IMHO.

A maioria dos casos de uso de genéricos que encontrei possui algum tipo de supertipo genérico, por exemplo, List<T>para ArrayList<T>ou GenericDAO<T>para DAO<T>etc.

Solução Java pura

O artigo Acessando tipos genéricos em tempo de execução em Java explica como você pode fazer isso usando Java puro.

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

Solução de mola

Meu projeto estava usando o Spring, o que é ainda melhor, pois o Spring tem um método utilitário útil para encontrar o tipo. Essa é a melhor abordagem para mim, pois parece mais arrumada. Acho que se você não estivesse usando o Spring, poderia escrever seu próprio método utilitário.

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
fonte
1
por favor esclarecer o significado de resolveTypeArgument argumentos
gstackoverflow
getClass () é um método de java.lang.Object que retornará a classe do objeto específico em tempo de execução; este é o objeto para o qual você deseja resolver o tipo. AbstractHibernateDao.class é apenas o nome da classe base ou superclasse da hierarquia de classes de tipo genérico. A declaração de importação está incluída para que você possa encontrar facilmente os documentos e verificar a si mesmo. Esta é a página docs.spring.io/spring/docs/current/javadoc-api/org/…
Ben Thurley
Qual é a solução de primavera com a versão 4.3.6 e superior. Não funciona com a primavera 4.3.6.
Erlan
1
O link em "solução Pure Java" está quebrado, é agora blog.xebia.com/acessing-generic-types-at-runtime-in-java
Nick Breen
1
@ AlikElzin-kilaka, é inicializado no construtor usando a classe Spring GenericTypeResolver.
Ben Thurley
103

No entanto, há uma pequena brecha: se você definir sua Fooclasse como abstrata. Isso significa que você precisa instanciar sua classe como:

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

(Observe as chaves duplas no final.)

Agora você pode recuperar o tipo de Tem tempo de execução:

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

Observe, no entanto, que mySuperclassdeve ser a superclasse da definição de classe que realmente define o tipo final para T.

Também não é muito elegante, mas você deve decidir se prefere new Foo<MyType>(){}ou new Foo<MyType>(MyType.class);no seu código.


Por exemplo:

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

Então:

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
fonte
5
Esta é facilmente a melhor resposta aqui! Além disso, para o que vale a pena, esta é a estratégia que o Google Guice usa para classes de ligação comTypeLiteral
ATG
14
Observe que toda vez que esse método de construção de objeto é usado, uma nova classe anônima é criada. Em outras palavras, dois objetos ae bcriados dessa maneira estenderão a mesma classe, mas não terão classes de instância idênticas. a.getClass() != b.getClass()
Martin Serrano
3
Há um cenário em que isso não funciona. Se o Foo implementasse uma interface, como Serializable, a classe anônima não seria Serializable, a menos que a instanciação da classe seja. Tentei contorná-lo criando uma classe de fábrica serializável que cria a classe anônima derivada do Foo, mas, por algum motivo, getActualTypeArguments retorna o tipo genérico em vez da classe real. Por exemplo: (new FooFactory <MyType> ()). CreateFoo ()
Lior Chaga
38

Uma abordagem / solução alternativa / solução padrão é adicionar um classobjeto ao (s) construtor (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();
    }
 }
Andreas Dolk
fonte
1
Mas parecia que o @autowired em uso real não pode ser contornado?
Alfred Huang
@AlfredHuang A solução seria criar um bean para a classe que faz isso e não depender da fiação automática.
Calebj
20

Imagine que você tem uma superclasse abstrata que é genérica:

public abstract class Foo<? extends T> {}

E então você tem uma segunda classe que estende Foo com uma barra genérica que estende T:

public class Second extends Foo<Bar> {}

Você pode obter a classe Bar.classna classe Foo selecionando a Type(da resposta de bert bruynooghe) e inferindo-a usando a Classinstância:

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

Você deve observar que essa operação não é ideal; portanto, é uma boa idéia armazenar em cache o valor calculado para evitar vários cálculos. Um dos usos típicos está na implementação genérica do DAO.

A implementação 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;
    }
}

O valor retornado é Bar.class quando invocado da classe Foo em outra função ou da classe Bar.

droidpl
fonte
1
toString().split(" ")[1]esse era o problema, evite o"class "
IgniteCoders
16

Aqui está uma solução de trabalho:

@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: Pode ser usado apenas como superclasse

  1. Tem que ser estendido com classe digitada ( Child extends Generic<Integer>)

OU

  1. Tem que ser criado como implementação anônima ( new Generic<Integer>() {};)
Yaroslav Kovbas
fonte
3
getTypeName chama toString, portanto, pode ser substituído por .getActualTypeArguments () [0] .toString ();
Yaroslav Kovbas
9

Uma rota melhor do que a classe sugerida pelos outros é passar um objeto que possa fazer o que você faria com a classe, por exemplo, criar uma nova instância.

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
fonte
@ Ricky Clarkson: Não vejo como esta fábrica deve devolver foos parametrizados. Poderia, por favor, explicar como você tira o Foo <T> disso? Parece-me que isso dá apenas Foo não parametrizado. O T no make10 não é simplesmente Foo aqui?
ib84 13/03
@ ib84 Corrigi o código; Parece que eu perdi o fato de o Foo ter sido parametrizado quando escrevi a resposta originalmente.
Ricky Clarkson
9

Eu tive esse problema em uma classe genérica abstrata. Nesse caso específico, a solução é mais simples:

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

e mais tarde na classe derivada:

class Bar extends Foo<Whatever> {
    @Override
    Class<T> getTClass() {
        return Whatever.class;
    }
}
user1154664
fonte
Sim, mas gostaria de deixar o mínimo necessário para estender essa classe. Verifique a resposta do
droidpl
5

Eu tenho uma solução (feia, mas eficaz) para esse problema, que usei recentemente:

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 __()
    {
    }
}
desconhecido6656
fonte
3

Eu encontrei uma maneira genérica e simples de fazer isso. Na minha classe, criei um método que retorna o tipo genérico de acordo com sua posição na definição de classe. Vamos assumir uma definição de classe como esta:

public class MyClass<A, B, C> {

}

Agora vamos criar alguns atributos para persistir os 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)

    } 

Em seguida, você pode criar um método genérico que retorne o tipo com base no índice da definição 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, no construtor, basta chamar o método e enviar o índice para cada tipo. O código completo deve se parecer com:

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
fonte
2

Conforme explicado em outras respostas, para usar essa ParameterizedTypeabordagem, você precisa estender a classe, mas isso parece um trabalho extra para criar uma nova classe que a estenda ...

Portanto, tornar a classe abstrata, obriga a estendê-la, satisfazendo, assim, o requisito de subclassificação. (usando @Getter do lombok).

@Getter
public abstract class ConfigurationDefinition<T> {

    private Class<T> type;
    ...

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

Agora, estenda-o sem definir uma nova classe. (Observe o {} no final ... estendido, mas não substitua nada - a menos que você queira).

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

Presumo que, como você tem uma classe genérica, você teria uma variável assim:

private T t;

(essa variável precisa assumir um valor no construtor)

Nesse caso, você pode simplesmente criar o seguinte método:

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

Espero que ajude!

Pontios
fonte
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
fonte
1

Se você estiver estendendo ou implementando qualquer classe / interface que esteja usando genéricos, poderá obter o Tipo Genérico da classe / interface pai, sem modificar nenhuma classe / interface existente.

Pode haver três possibilidades,

Caso 1 Quando sua classe está estendendo uma classe 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 Quando sua classe estiver implementando uma interface que esteja 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 Quando sua interface estiver estendendo uma interface que esteja 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
fonte
1

Isso é bem direto. Se você precisar da mesma classe:

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
fonte
0

Na verdade, suponho que você tenha um campo na sua classe do tipo T. Se não houver um campo do tipo T, qual é o sentido de ter um tipo genérico? Portanto, você pode simplesmente fazer uma instância desse campo.

No meu caso, eu tenho um

Listar itens <T>;
na minha turma e verifico se o tipo de turma é "Localidade" por

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

Obviamente, isso só funciona se o número total de classes possíveis for limitado.

Christine
fonte
4
O que devo fazer se items.isEmpty () for verdadeiro?
chaotic3quilibrium
0

Esta pergunta é antiga, mas agora o melhor é usar o google Gson.

Um exemplo para personalizar viewModel.

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

Classe 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
fonte
0

Eu queria passar T.class para um método que faça uso de Generics

O método readFile lê um arquivo .csv especificado pelo fileName com caminho completo. Pode haver arquivos CSV com conteúdos diferentes, portanto, eu preciso passar a classe de arquivo de modelo para que eu possa obter os objetos apropriados. Como isso está lendo o arquivo csv, eu queria fazer de uma maneira genérica. Por algum motivo ou outro, nenhuma das soluções acima funcionou para mim. Eu preciso usar Class<? extends T> typepara fazê-lo funcionar. Eu uso a biblioteca opencsv para analisar os arquivos 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;
}

É assim que o método readFile é chamado

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

Estou usando uma solução alternativa para isso:

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

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