Yo tengo;
List<String> stringList = new ArrayList<String>();
List<Integer> integerList = new ArrayList<Integer>();
¿Hay alguna forma (fácil) de recuperar el tipo genérico de la lista?
Yo tengo;
List<String> stringList = new ArrayList<String>();
List<Integer> integerList = new ArrayList<Integer>();
¿Hay alguna forma (fácil) de recuperar el tipo genérico de la lista?
Respuestas:
Si esos son realmente campos de una determinada clase, puede obtenerlos con una pequeña ayuda de reflexión:
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.
}
}
También puede hacerlo para los tipos de parámetros y el tipo de método de retorno.
Pero si están dentro del mismo alcance de la clase / método donde necesita saber sobre ellos, entonces no tiene sentido conocerlos, porque ya los ha declarado usted mismo.
<?>
(tipo comodín) en lugar de <Class>
(tipo de clase). Reemplace su <?>
por <Class>
o lo que sea <E>
.
También puede hacer lo mismo para los parámetros del 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
Respuesta corta: no.
Probablemente sea un duplicado, no puedo encontrar uno apropiado en este momento.
Java usa algo llamado borrado de tipo, lo que significa que en tiempo de ejecución ambos objetos son equivalentes. El compilador sabe que las listas contienen enteros o cadenas y, como tal, pueden mantener un entorno de tipo seguro. Esta información se pierde (en base a una instancia de objeto) en tiempo de ejecución, y la lista solo contiene 'Objetos'.
PUEDE averiguar un poco sobre la clase, los tipos por los que podría parametrizarse, pero normalmente esto es cualquier cosa que extienda "Objeto", es decir, cualquier cosa. Si define un tipo como
class <A extends MyClass> AClass {....}
AClass.class solo contendrá el hecho de que el parámetro A está limitado por MyClass, pero más que eso, no hay forma de saberlo.
List<Integer>
y List<String>
; en esos casos, el borrado de tipo no se aplica.
El tipo genérico de una colección solo debería importar si realmente tiene objetos, ¿verdad? Entonces, ¿no es más fácil simplemente hacer:
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
No existe un tipo genérico en tiempo de ejecución, pero se garantiza que los objetos dentro del tiempo de ejecución serán del mismo tipo que el genérico declarado, por lo que es bastante fácil probar la clase del elemento antes de procesarlo.
Otra cosa que puede hacer es simplemente procesar la lista para obtener miembros que sean del tipo correcto, ignorando a otros (o procesándolos de manera 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());
Esto le dará una lista de todos los elementos cuyas clases eran subclases de las Number
cuales luego puede procesar según lo necesite. El resto de los elementos se filtraron a otras listas. Debido a que están en el mapa, puede procesarlos como desee o ignorarlos.
Si desea ignorar por completo los elementos de otras clases, se vuelve mucho más simple:
List<Number> numbers = myCollection.stream()
.filter(Number.class::isInstance)
.map(Number.class::cast)
.collect(Collectors.toList());
Incluso puede crear un método de utilidad para asegurarse de que una lista contenga SOLAMENTE aquellos elementos que coincidan con una clase 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
sy cuando los retire de la lista, se los entregaría a un controlador apropiado según su tipo.
Para encontrar el tipo genérico de un campo:
((Class)((ParameterizedType)field.getGenericType()).getActualTypeArguments()[0]).getSimpleName()
Si necesita obtener el tipo genérico de un tipo devuelto, utilicé este enfoque cuando necesitaba encontrar métodos en una clase que devolviera ay Collection
luego acceder a sus 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]);
}
}
}
}
}
}
Esto produce:
El tipo genérico es la clase java.lang.String
Ampliando la respuesta 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);
}
}
En tiempo de ejecución, no, no puedes.
Sin embargo, a través de la reflexión, los parámetros de tipo son accesibles. Tratar
for(Field field : this.getDeclaredFields()) {
System.out.println(field.getGenericType())
}
El método getGenericType()
devuelve un objeto Type. En este caso, será una instancia de ParametrizedType
, que a su vez tiene métodos getRawType()
(que contendrá List.class
, en este caso) y getActualTypeArguments()
, que devolverá una matriz (en este caso, de longitud uno, que contiene uno String.class
o Integer.class
).
method.getGenericParameterTypes()
para obtener los tipos declarados de los parámetros de un método.
Tuve el mismo problema, pero usé instanceof en su lugar. Lo hizo de esta manera:
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");
}
}
}
Esto implica el uso de conversiones no controladas, así que solo haz esto cuando sepas que es una lista y de qué tipo puede ser.
Generalmente imposible, porque List<String>
y List<Integer>
comparte la misma clase de tiempo de ejecución.
Sin embargo, es posible que pueda reflexionar sobre el tipo declarado del campo que contiene la lista (si el tipo declarado no se refiere a un parámetro de tipo cuyo valor no conoce).
Como otros han dicho, la única respuesta correcta es no, el tipo ha sido borrado.
Si la lista tiene un número de elementos distinto de cero, puede investigar el tipo del primer elemento (utilizando su método getClass, por ejemplo). Eso no le dirá el tipo genérico de la lista, pero sería razonable suponer que el tipo genérico era una superclase de los tipos de la lista.
No recomendaría el enfoque, pero en un apuro podría ser útil.
List<Integer>
y List<String>
; en esos casos, el borrado de tipo no 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 no se puede encontrar
El tipo se borra, por lo que no podrá hacerlo. Ver http://en.wikipedia.org/wiki/Type_erasure y http://en.wikipedia.org/wiki/Generics_in_Java#Type_erasure
List<Integer>
y List<String>
; en esos casos, el borrado de tipo no se aplica.
Use Reflection para obtener Field
estos, entonces simplemente puede hacer: field.genericType
para obtener el tipo que contiene la información sobre genéricos también.