Eu tenho;
List<String> stringList = new ArrayList<String>();
List<Integer> integerList = new ArrayList<Integer>();
Existe uma maneira (fácil) de recuperar o tipo genérico da lista?
Eu tenho;
List<String> stringList = new ArrayList<String>();
List<Integer> integerList = new ArrayList<Integer>();
Existe uma maneira (fácil) de recuperar o tipo genérico da lista?
Respostas:
Se esses são realmente os campos de uma determinada classe, você pode obtê-los com uma pequena ajuda de reflexão:
package test;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.List;
public class Test {
List<String> stringList = new ArrayList<String>();
List<Integer> integerList = new ArrayList<Integer>();
public static void main(String... args) throws Exception {
Field stringListField = Test.class.getDeclaredField("stringList");
ParameterizedType stringListType = (ParameterizedType) stringListField.getGenericType();
Class<?> stringListClass = (Class<?>) stringListType.getActualTypeArguments()[0];
System.out.println(stringListClass); // class java.lang.String.
Field integerListField = Test.class.getDeclaredField("integerList");
ParameterizedType integerListType = (ParameterizedType) integerListField.getGenericType();
Class<?> integerListClass = (Class<?>) integerListType.getActualTypeArguments()[0];
System.out.println(integerListClass); // class java.lang.Integer.
}
}
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.
<?>
(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:
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
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 extends MyClass> 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.
List<Integer>
e List<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:
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 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:
List<Number> numbers = myCollection.stream()
.filter(Number.class::isInstance)
.map(Number.class::cast)
.collect(Collectors.toList());
Você pode até criar um método utilitário para garantir que uma lista contenha APENAS os itens correspondentes a uma classe específica:
public <V> List<V> getTypeSafeItemList(Collection<Object> input, Class<V> cls) {
return input.stream()
.filter(cls::isInstance)
.map(cls::cast)
.collect(Collectors.toList());
}
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:
((Class)((ParameterizedType)field.getGenericType()).getActualTypeArguments()[0]).getSimpleName()
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:
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.List;
public class Test {
public List<String> test() {
return null;
}
public static void main(String[] args) throws Exception {
for (Method method : Test.class.getMethods()) {
Class returnClass = method.getReturnType();
if (Collection.class.isAssignableFrom(returnClass)) {
Type returnType = method.getGenericReturnType();
if (returnType instanceof ParameterizedType) {
ParameterizedType paramType = (ParameterizedType) returnType;
Type[] argTypes = paramType.getActualTypeArguments();
if (argTypes.length > 0) {
System.out.println("Generic type is " + argTypes[0]);
}
}
}
}
}
}
Isso gera:
Tipo genérico é a classe java.lang.String
Expandindo a resposta de Steve K:
/**
* 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:
protected WhateverClass add(List<?> data) {//For fluant useage
if(data==null) || data.isEmpty(){
throw new IllegalArgumentException("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);
}
}
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.class
ou Integer.class
).
method.getGenericParameterTypes()
para obter os tipos declarados dos parâmetros de um método.
Teve o mesmo problema, mas usei instanceof. Fez assim:
List<Object> listCheck = (List<Object>)(Object) stringList;
if (!listCheck.isEmpty()) {
if (listCheck.get(0) instanceof String) {
System.out.println("List type is String");
}
if (listCheck.get(0) instanceof Integer) {
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.
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.
List<Integer>
e List<String>
; nesses casos, o apagamento do tipo não se aplica.
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;
public class GenericTypeOfCollectionTest {
public class FormBean {
}
public class MyClazz {
private List<FormBean> list = new ArrayList<FormBean>();
}
@Test
public void testName() throws Exception {
Field[] fields = MyClazz.class.getFields();
for (Field field : fields) {
//1. Check if field is of Collection Type
if (Collection.class.isAssignableFrom(field.getType())) {
//2. Get Generic type of your field
Class fieldGenericType = getFieldGenericType(field);
//3. Compare with <FromBean>
Assert.assertTrue("List<FormBean>",
FormBean.class.isAssignableFrom(fieldGenericType));
}
}
}
//Returns generic type of any field
public Class 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 & FormBean
return new Boolean(false).getClass();
}
}
getFieldGenericTypefieldGenericType
que não pode ser encontrado
O 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
List<Integer>
e List<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.