Ser capaz de inspecionar programaticamente um objeto List e ver seu tipo genérico. Um método pode querer inserir objetos com base no tipo genérico da coleção. Isso é possível em idiomas que implementam genéricos em tempo de execução, em vez de tempo de compilação.
21411 Steve Kuo
4
Certo - a única maneira de permitir a detecção de tempo de execução é subclassificando: na verdade, você PODE estender o tipo genérico e, em seguida, usar a declaração de tipo de localização por reflexão usada pelo subtipo. Isso é um pouco de reflexão, mas possível. Infelizmente, não há uma maneira fácil de impor que seja necessário usar uma subclasse genérica.
StaxMan
1
Certamente stringList contém strings e integerList inteiros? Por que torná-lo mais complicado?
Ben Thurley 16/09
Respostas:
408
Se esses são realmente os campos de uma determinada classe, você pode obtê-los com uma pequena ajuda de reflexão:
Você também pode fazer isso para tipos de parâmetros e retornar tipos de métodos.
Mas se eles estiverem dentro do mesmo escopo da classe / método em que você precisa conhecê-los, não faz sentido conhecê-los, porque você já os declarou.
@ Oleg Sua variável está usando <?>(tipo curinga) em vez de <Class>(tipo de classe). Substitua seu <?>por <Class>ou o que quer <E>.
BalusC
19
Você também pode fazer o mesmo para os parâmetros do método:
Type[] types = method.getGenericParameterTypes();//Now assuming that the first parameter to the method is of type List<Integer>ParameterizedType pType =(ParameterizedType) types[0];Class<?> clazz =(Class<?>) pType.getActualTypeArguments()[0];System.out.println(clazz);//prints out java.lang.Integer
Em method.getGenericParameterTypes (); o que é método?
Neo Ravi
18
Resposta curta: não.
Provavelmente é uma duplicata, mas não é possível encontrar uma apropriada no momento.
Java usa algo chamado apagamento de tipo, o que significa que no tempo de execução ambos os objetos são equivalentes. O compilador sabe que as listas contêm números inteiros ou seqüências de caracteres e, como tal, pode manter um ambiente seguro de tipo. Essas informações são perdidas (em uma instância de objeto) no tempo de execução e a lista contém apenas 'Objetos'.
Você PODE descobrir um pouco sobre a classe, por quais tipos ela pode ser parametrizada, mas normalmente isso é tudo o que estende "Objeto", ou seja, qualquer coisa. Se você definir um tipo como
class<A extendsMyClass>AClass{....}
AClass.class conterá apenas o fato de que o parâmetro A é delimitado por MyClass, mas mais do que isso, não há como saber.
Isso será verdade se a classe concreta do genérico não for especificada; no exemplo dele, ele está declarando explicitamente as listas como List<Integer>e List<String>; nesses casos, o apagamento do tipo não se aplica.
Haroldo_OK
13
O tipo genérico de uma coleção só deve importar se realmente tiver objetos, certo? Portanto, não é mais fácil apenas fazer:
Collection<?> myCollection = getUnknownCollectionFromSomewhere();Class genericClass =null;Iterator it = myCollection.iterator();if(it.hasNext()){
genericClass = it.next().getClass();}if(genericClass !=null){//do whatever we needed to know the type for
Não existe um tipo genérico no tempo de execução, mas é garantido que os objetos contidos no tempo de execução sejam do mesmo tipo que o genérico declarado; portanto, é fácil testar a classe do item antes de processá-lo.
Outra coisa que você pode fazer é simplesmente processar a lista para obter membros do tipo certo, ignorando outros (ou processando-os de maneira diferente).
Map<Class<?>,List<Object>> classObjectMap = myCollection.stream().filter(Objects::nonNull).collect(Collectors.groupingBy(Object::getClass));// Process the list of the correct class, and/or handle objects of incorrect// class (throw exceptions, etc). You may need to group subclasses by// filtering the keys. For instance:List<Number> numbers = classObjectMap.entrySet().stream().filter(e->Number.class.isAssignableFrom(e.getKey())).flatMap(e->e.getValue().stream()).map(Number.class::cast).collect(Collectors.toList());
Isso fornecerá uma lista de todos os itens cujas classes eram subclasses dos Numberquais você poderá processar conforme necessário. O restante dos itens foi filtrado para outras listas. Como eles estão no mapa, você pode processá-los conforme desejado ou ignorá-los.
Se você deseja ignorar itens de outras classes por completo, fica muito mais simples:
E se você tem uma coleção vazia? Ou se você tiver uma coleção <object> com um número inteiro e uma string?
Falci 14/05
O contrato para a classe pode exigir que apenas listas de objetos finais sejam passadas e que a chamada do método retorne nulo se a lista for nula, vazia ou se o tipo de lista for inválido.
precisa saber é o seguinte
1
No caso de uma coleção vazia, é seguro atribuir a qualquer tipo de lista em tempo de execução, porque não há nada e, após o apagamento do tipo, uma Lista é uma Lista é uma Lista. Por causa disso, não consigo pensar em nenhuma instância em que o tipo de uma coleção vazia seja importante.
Steve K
Bem, "poderia importar" se você mantivesse a referência em um encadeamento e a processasse quando os objetos começassem a aparecer em um cenário de consumidor produtor. Se a lista apresentasse os tipos de objetos incorretos, coisas ruins poderiam acontecer. Acho que para impedir que você precise interromper o processamento dormindo até haver pelo menos um item na lista antes de continuar.
ggb667
Se você tiver esse tipo de cenário produtor / consumidor, o planejamento da lista para ter um determinado tipo genérico sem ter certeza de que o que poderia ser colocado na lista é de design ruim. Em vez disso, você a declararia como uma coleção de Objects e, quando as retirar da lista, as entregaria a um manipulador apropriado com base no tipo delas.
Se você precisar obter o tipo genérico de um tipo retornado, usei essa abordagem quando precisei encontrar métodos em uma classe que retornasse um Collectione depois acesse seus tipos genéricos:
/**
* Performs a forced cast.
* Returns null if the collection type does not match the items in the list.
* @param data The list to cast.
* @param listType The type of list to cast to.
*/static<T>List<?super T> castListSafe(List<?> data,Class<T> listType){List<T> retval =null;//This test could be skipped if you trust the callers, but it wouldn't be safe then.if(data!=null&&!data.isEmpty()&& listType.isInstance(data.iterator().next().getClass())){@SuppressWarnings("unchecked")//It's OK, we know List<T> contains the expected type.List<T> foo =(List<T>)data;return retval;}return retval;}Usage:protectedWhateverClass add(List<?> data){//For fluant useageif(data==null)|| data.isEmpty(){thrownewIllegalArgumentException("add() "+ data==null?"null":"empty"+" collection");}Class<?> colType = data.iterator().next().getClass();//Something
aMethod(castListSafe(data, colType));}
aMethod(List<Foo> foo){for(Foo foo:List){System.out.println(Foo);}}
aMethod(List<Bar> bar){for(Bar bar:List){System.out.println(Bar);}}
Mesmos problemas da resposta de Steve K: E se a lista estiver vazia? E se a lista for do tipo <Object> que contém uma String e um Inteiro? Seu código significa que você só pode adicionar mais seqüências de caracteres se o primeiro item da lista for uma sequência e nenhum número inteiro ...
subrunner
Se a lista estiver vazia, você estará sem sorte. Se a lista for Objeto e realmente contiver um Objeto, você estará bem, mas se houver uma mistura de coisas, você estará sem sorte. Apagar significa que isso é tão bom quanto você pode fazer. O apagamento está faltando na minha opinião. As ramificações dele são ao mesmo tempo pouco compreendidas e insuficientes para abordar os casos de uso interessantes e desejados de interesse típico. Nada impede a criação de uma classe que pode ser perguntada sobre o tipo, mas simplesmente não é assim que as classes incorporadas funcionam. Coleções deve saber o que eles contêm, mas infelizmente ...
ggb667
4
Em tempo de execução, não, você não pode.
No entanto, através da reflexão, os parâmetros de tipo são acessíveis. Experimentar
for(Field field :this.getDeclaredFields()){System.out.println(field.getGenericType())}
O método getGenericType()retorna um objeto Type. Nesse caso, será uma instância de ParametrizedType, que por sua vez possui métodos getRawType()(que conterão List.class, nesse caso) e getActualTypeArguments()que retornará uma matriz (nesse caso, de comprimento um, contendo um String.classou Integer.class).
Teve o mesmo problema, mas usei instanceof. Fez assim:
List<Object> listCheck =(List<Object>)(Object) stringList;if(!listCheck.isEmpty()){if(listCheck.get(0)instanceofString){System.out.println("List type is String");}if(listCheck.get(0)instanceofInteger){System.out.println("List type is Integer");}}}
Isso envolve o uso de projeções não verificadas, portanto, faça isso somente quando você souber que é uma lista e que tipo pode ser.
Geralmente impossível, porque List<String>e List<Integer>compartilham a mesma classe de tempo de execução.
Você pode refletir sobre o tipo declarado do campo que contém a lista (se o tipo declarado não se referir a um parâmetro de tipo cujo valor você não conhece).
Como outros já disseram, a única resposta correta é não, o tipo foi apagado.
Se a lista tiver um número diferente de zero de elementos, você poderá investigar o tipo do primeiro elemento (usando o método getClass, por exemplo). Isso não informa o tipo genérico da lista, mas seria razoável supor que o tipo genérico fosse uma superclasse dos tipos na lista.
Eu não defenderia a abordagem, mas em um momento difícil poderia ser útil.
Isso será verdade se a classe concreta do genérico não for especificada; no exemplo dele, ele está declarando explicitamente as listas como List<Integer>e List<String>; nesses casos, o apagamento do tipo não se aplica.
Haroldo_OK
2
import org.junit.Assert;import org.junit.Test;import java.lang.reflect.Field;import java.lang.reflect.ParameterizedType;import java.util.ArrayList;import java.util.Collection;import java.util.List;publicclassGenericTypeOfCollectionTest{publicclassFormBean{}publicclassMyClazz{privateList<FormBean> list =newArrayList<FormBean>();}@Testpublicvoid testName()throwsException{Field[] fields =MyClazz.class.getFields();for(Field field : fields){//1. Check if field is of Collection Typeif(Collection.class.isAssignableFrom(field.getType())){//2. Get Generic type of your fieldClass fieldGenericType = getFieldGenericType(field);//3. Compare with <FromBean>Assert.assertTrue("List<FormBean>",FormBean.class.isAssignableFrom(fieldGenericType));}}}//Returns generic type of any fieldpublicClass getFieldGenericType(Field field){if(ParameterizedType.class.isAssignableFrom(field.getGenericType().getClass())){ParameterizedType genericType =(ParameterizedType) field.getGenericType();return((Class)(genericType.getActualTypeArguments()[0])).getSuperclass();}//Returns dummy Boolean Class to compare with ValueObject & FormBeanreturnnewBoolean(false).getClass();}}
Isso será verdade se a classe concreta do genérico não for especificada; no exemplo dele, ele está declarando explicitamente as listas como List<Integer>e List<String>; nesses casos, o apagamento do tipo não se aplica.
Haroldo_OK
0
Use o Reflection para obter os itens Fieldpara estes, então você pode apenas fazer: field.genericTypepara obter o tipo que contém as informações sobre os genéricos também.
Respostas:
Se esses são realmente os campos de uma determinada classe, você pode obtê-los com uma pequena ajuda de reflexão:
Você também pode fazer isso para tipos de parâmetros e retornar tipos de métodos.
Mas se eles estiverem dentro do mesmo escopo da classe / método em que você precisa conhecê-los, não faz sentido conhecê-los, porque você já os declarou.
fonte
<?>
(tipo curinga) em vez de<Class>
(tipo de classe). Substitua seu<?>
por<Class>
ou o que quer<E>
.Você também pode fazer o mesmo para os parâmetros do método:
fonte
Resposta curta: não.
Provavelmente é uma duplicata, mas não é possível encontrar uma apropriada no momento.
Java usa algo chamado apagamento de tipo, o que significa que no tempo de execução ambos os objetos são equivalentes. O compilador sabe que as listas contêm números inteiros ou seqüências de caracteres e, como tal, pode manter um ambiente seguro de tipo. Essas informações são perdidas (em uma instância de objeto) no tempo de execução e a lista contém apenas 'Objetos'.
Você PODE descobrir um pouco sobre a classe, por quais tipos ela pode ser parametrizada, mas normalmente isso é tudo o que estende "Objeto", ou seja, qualquer coisa. Se você definir um tipo como
AClass.class conterá apenas o fato de que o parâmetro A é delimitado por MyClass, mas mais do que isso, não há como saber.
fonte
List<Integer>
eList<String>
; nesses casos, o apagamento do tipo não se aplica.O tipo genérico de uma coleção só deve importar se realmente tiver objetos, certo? Portanto, não é mais fácil apenas fazer:
Não existe um tipo genérico no tempo de execução, mas é garantido que os objetos contidos no tempo de execução sejam do mesmo tipo que o genérico declarado; portanto, é fácil testar a classe do item antes de processá-lo.
Outra coisa que você pode fazer é simplesmente processar a lista para obter membros do tipo certo, ignorando outros (ou processando-os de maneira diferente).
Isso fornecerá uma lista de todos os itens cujas classes eram subclasses dos
Number
quais você poderá processar conforme necessário. O restante dos itens foi filtrado para outras listas. Como eles estão no mapa, você pode processá-los conforme desejado ou ignorá-los.Se você deseja ignorar itens de outras classes por completo, fica muito mais simples:
Você pode até criar um método utilitário para garantir que uma lista contenha APENAS os itens correspondentes a uma classe específica:
fonte
Object
s e, quando as retirar da lista, as entregaria a um manipulador apropriado com base no tipo delas.Para encontrar o tipo genérico de um campo:
fonte
Se você precisar obter o tipo genérico de um tipo retornado, usei essa abordagem quando precisei encontrar métodos em uma classe que retornasse um
Collection
e depois acesse seus tipos genéricos:Isso gera:
fonte
Expandindo a resposta de Steve K:
fonte
Em tempo de execução, não, você não pode.
No entanto, através da reflexão, os parâmetros de tipo são acessíveis. Experimentar
O método
getGenericType()
retorna um objeto Type. Nesse caso, será uma instância deParametrizedType
, que por sua vez possui métodosgetRawType()
(que conterãoList.class
, nesse caso) egetActualTypeArguments()
que retornará uma matriz (nesse caso, de comprimento um, contendo umString.class
ouInteger.class
).fonte
method.getGenericParameterTypes()
para obter os tipos declarados dos parâmetros de um método.Teve o mesmo problema, mas usei instanceof. Fez assim:
Isso envolve o uso de projeções não verificadas, portanto, faça isso somente quando você souber que é uma lista e que tipo pode ser.
fonte
Geralmente impossível, porque
List<String>
eList<Integer>
compartilham a mesma classe de tempo de execução.Você pode refletir sobre o tipo declarado do campo que contém a lista (se o tipo declarado não se referir a um parâmetro de tipo cujo valor você não conhece).
fonte
Como outros já disseram, a única resposta correta é não, o tipo foi apagado.
Se a lista tiver um número diferente de zero de elementos, você poderá investigar o tipo do primeiro elemento (usando o método getClass, por exemplo). Isso não informa o tipo genérico da lista, mas seria razoável supor que o tipo genérico fosse uma superclasse dos tipos na lista.
Eu não defenderia a abordagem, mas em um momento difícil poderia ser útil.
fonte
List<Integer>
eList<String>
; nesses casos, o apagamento do tipo não se aplica.fonte
getFieldGenericTypefieldGenericType
que não pode ser encontradoO tipo é apagado para que você não possa. Veja http://en.wikipedia.org/wiki/Type_erasure e http://en.wikipedia.org/wiki/Generics_in_Java#Type_erasure
fonte
List<Integer>
eList<String>
; nesses casos, o apagamento do tipo não se aplica.Use o Reflection para obter os itens
Field
para estes, então você pode apenas fazer:field.genericType
para obter o tipo que contém as informações sobre os genéricos também.fonte