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?
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,
classFoo<T>{finalClass<T> typeParameterClass;publicFoo(Class<T> typeParameterClass){this.typeParameterClass = typeParameterClass;}publicvoid bar(){// you can access the typeParameterClass here and do whatever you like}}
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.
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;publicabstractclassAbstractHibernateDao<T extendsDomainObject>implementsDataAccessObject<T>{@AutowiredprivateSessionFactory sessionFactory;privatefinalClass<T> genericType;privatefinalString RECORD_COUNT_HQL;privatefinalString FIND_ALL_HQL;@SuppressWarnings("unchecked")publicAbstractHibernateDao(){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 ";}
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.
@ 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 =newFoo<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.
*/publicabstractclassSilentStack<E>extendsArrayDeque<E>{public E pop(){try{returnsuper.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 ){returnnull;}}}
Então:
publicclassMain{// Note the braces...privateDeque<String> stack =newSilentStack<String>(){};publicstaticvoid main(String args[]){// Returns a new instance of String.String s = stack.pop();System.out.printf("s = '%s'\n", s );}}
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:
publicclassFoo<T>{privateClass<T> type;publicFoo(Class<T> type){this.type = type;}publicClass<T> getType(){return type;}public T newInstance(){return type.newInstance();}}
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:
publicabstractclassFoo<?extends T>{}
E então você tem uma segunda classe que estende Foo com uma barra genérica que estende T:
publicclassSecondextendsFoo<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 StringString 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.
toString().split(" ")[1]esse era o problema, evite o"class "
IgniteCoders
16
Aqui está uma solução de trabalho:
@SuppressWarnings("unchecked")privateClass<T> getGenericTypeClass(){try{String className =((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0].getTypeName();Class<?> clazz =Class.forName(className);return(Class<T>) clazz;}catch(Exception e){thrownewIllegalStateException("Class is not parametrized with generic type!!! Please use extends <> ");}}
NOTAS:
Pode ser usado apenas como superclasse
Tem que ser estendido com classe digitada ( Child extends Generic<Integer>)
OU
Tem que ser criado como implementação anônima ( new Generic<Integer>() {};)
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.
interfaceFactory<T>{
T apply();}<T>voidList<T> make10(Factory<T> factory){List<T> result =newArrayList<T>();for(int a =0; a <10; a++)
result.add(factory.apply());return result;}classFooFactory<T>implementsFactory<Foo<T>>{publicFoo<T> apply(){returnnewFoo<T>();}}List<Foo<Integer>> foos = make10(newFooFactory<Integer>());
@ 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:
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;publicstatic<T>Class<T> getGenericClass(){
__<T> ins =new __<T>();TypeVariable<?>[] cls = ins.getClass().getTypeParameters();return(Class<T>)cls[0].getClass();}privatefinalclass __<T>// generic helper class which does only provide type information{private __(){}}
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:
publicclassMyClass<A, B, C>{}
Agora vamos criar alguns atributos para persistir os tipos:
publicclassMyClass<A, B, C>{privateClass<A> aType;privateClass<B> bType;privateClass<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
*/privateType getGenericClassType(int index){// To make it use generics without supplying the class typeType type = getClass().getGenericSuperclass();while(!(type instanceofParameterizedType)){if(type instanceofParameterizedType){
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:
publicclassMyClass<A, B, C>{privateClass<A> aType;privateClass<B> bType;privateClass<C> cType;publicMyClass(){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
*/privateType getGenericClassType(int index){Type type = getClass().getGenericSuperclass();while(!(type instanceofParameterizedType)){if(type instanceofParameterizedType){
type =((Class<?>)((ParameterizedType) type).getRawType()).getGenericSuperclass();}else{
type =((Class<?>) type).getGenericSuperclass();}}return((ParameterizedType) type).getActualTypeArguments()[index];}}
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).
public<T> T yourMethodSignature(Class<T> type){// get some object and check the type match the given typeObject result =...if(type.isAssignableFrom(result.getClass())){return(T)result;}else{// handle the error}}
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
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 variableSystem.out.println("Class instance name: "+ typeClass.getName());}catch(ClassNotFoundException e){System.out.println("ClassNotFound!! Something wrong! "+ e.getMessage());}
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.
classGenericClass<T>(private val rawType:Class<*>){
constructor():this(`$Gson$Types`.getRawType(object :TypeToken<T>(){}.getType()))
fun getRawType():Class<T>{return rawType as Class<T>}}
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.
import com.fasterxml.jackson.core.type.TypeReference;
new TypeReference<T>(){}
Respostas:
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
Class
parâmetro do tipo para o construtor do tipo genérico, por exemplo,fonte
typeParameterClass
sem uma atribuição padrão no construtor é perfeitamente adequado. Não há necessidade de configurá-lo uma segunda vez.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>
paraArrayList<T>
ouGenericDAO<T>
paraDAO<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.
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.
fonte
No entanto, há uma pequena brecha: se você definir sua
Foo
classe como abstrata. Isso significa que você precisa instanciar sua classe como:(Observe as chaves duplas no final.)
Agora você pode recuperar o tipo de
T
em tempo de execução:Observe, no entanto, que
mySuperclass
deve ser a superclasse da definição de classe que realmente define o tipo final paraT
.Também não é muito elegante, mas você deve decidir se prefere
new Foo<MyType>(){}
ounew Foo<MyType>(MyType.class);
no seu código.Por exemplo:
Então:
fonte
TypeLiteral
a
eb
criados dessa maneira estenderão a mesma classe, mas não terão classes de instância idênticas.a.getClass() != b.getClass()
Uma abordagem / solução alternativa / solução padrão é adicionar um
class
objeto ao (s) construtor (es), como:fonte
Imagine que você tem uma superclasse abstrata que é genérica:
E então você tem uma segunda classe que estende Foo com uma barra genérica que estende T:
Você pode obter a classe
Bar.class
na classe Foo selecionando aType
(da resposta de bert bruynooghe) e inferindo-a usando aClass
instância: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:
O valor retornado é Bar.class quando invocado da classe Foo em outra função ou da classe Bar.
fonte
toString().split(" ")[1]
esse era o problema, evite o"class "
Aqui está uma solução de trabalho:
NOTAS: Pode ser usado apenas como superclasse
Child extends Generic<Integer>
)OU
new Generic<Integer>() {};
)fonte
Você não pode fazer isso por causa do apagamento do tipo. Consulte também a pergunta Stack Overflow Genéricos Java - apagamento de tipo - quando e o que acontece .
fonte
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.
fonte
Eu tive esse problema em uma classe genérica abstrata. Nesse caso específico, a solução é mais simples:
e mais tarde na classe derivada:
fonte
Eu tenho uma solução (feia, mas eficaz) para esse problema, que usei recentemente:
fonte
É possível:
Você precisa de duas funções no hibernate-generic-dao / blob / master / dao / src / main / java / com / googlecode / genericdao / dao / DAOUtil.java .
Para mais explicações, consulte Refletindo genéricos .
fonte
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:
Agora vamos criar alguns atributos para persistir os tipos:
Em seguida, você pode criar um método genérico que retorne o tipo com base no índice da definição genérica:
Finalmente, no construtor, basta chamar o método e enviar o índice para cada tipo. O código completo deve se parecer com:
fonte
Conforme explicado em outras respostas, para usar essa
ParameterizedType
abordagem, 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).
Agora, estenda-o sem definir uma nova classe. (Observe o {} no final ... estendido, mas não substitua nada - a menos que você queira).
fonte
Presumo que, como você tem uma classe genérica, você teria uma variável assim:
(essa variável precisa assumir um valor no construtor)
Nesse caso, você pode simplesmente criar o seguinte método:
Espero que ajude!
fonte
fonte
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
Caso 2 Quando sua classe estiver implementando uma interface que esteja usando Genéricos
Caso 3 Quando sua interface estiver estendendo uma interface que esteja usando Genéricos
fonte
Isso é bem direto. Se você precisar da mesma classe:
fonte
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
na minha turma e verifico se o tipo de turma é "Localidade" porObviamente, isso só funciona se o número total de classes possíveis for limitado.
fonte
Esta pergunta é antiga, mas agora o melhor é usar o google
Gson
.Um exemplo para personalizar
viewModel
.Classe de tipo genérico
fonte
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> type
para fazê-lo funcionar. Eu uso a biblioteca opencsv para analisar os arquivos CSV.É assim que o método readFile é chamado
fonte
Estou usando uma solução alternativa para isso:
fonte