Obtener el tipo genérico de java.util.List

Resuelto Thizzer asked hace 14 años • 15 respuestas

Tengo;

List<String> stringList = new ArrayList<String>();
List<Integer> integerList = new ArrayList<Integer>();

¿Existe una manera (fácil) de recuperar el tipo genérico de la lista?

Thizzer avatar Dec 22 '09 04:12 Thizzer
Aceptado

Si esos son en realidad campos de una determinada clase, entonces puedes obtenerlos con un poco de ayuda de la reflexión:

package com.stackoverflow.q1942644;

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<>();
    List<Integer> integerList = new ArrayList<>();

    public static void main(String... args) throws Exception {
        Class<Test> testClass = Test.class;

        Field stringListField = testClass.getDeclaredField("stringList");
        ParameterizedType stringListType = (ParameterizedType) stringListField.getGenericType();
        Class<?> stringListClass = (Class<?>) stringListType.getActualTypeArguments()[0];
        System.out.println(stringListClass); // class java.lang.String

        Field integerListField = testClass.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 retorno de los métodos.

Pero si están dentro del mismo alcance de la clase/método donde necesitas saber sobre ellos, entonces no tiene sentido conocerlos, porque ya los has declarado tú mismo.

BalusC avatar Dec 21 '2009 21:12 BalusC

También puedes hacer lo mismo con los parámetros del método:

Method method = someClass.getDeclaredMethod("someMethod");
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
tsaixingwei avatar Feb 27 '2011 14:02 tsaixingwei

Respuesta corta: no.

Probablemente sea un duplicado; no puedo encontrar uno apropiado en este momento.

Java usa algo llamado borrado de tipos, lo que significa que en tiempo de ejecución ambos objetos son equivalentes. El compilador sabe que las listas contienen números enteros o cadenas y, como tal, puede mantener un entorno de tipos seguros. Esta información se pierde (por instancia de objeto) en tiempo de ejecución y la lista solo contiene "Objetos".

PUEDES descubrir un poco sobre la clase, qué tipos pueden parametrizarla, 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.

falstro avatar Dec 21 '2009 21:12 falstro

El tipo genérico de una colección sólo debería importar si realmente contiene 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 internos en 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 puedes hacer es simplemente procesar la lista para obtener miembros que sean del tipo correcto, ignorando a los demás (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 Numberlas cuales luego podrá procesar según sea necesario. El resto de los elementos se filtraron en otras listas. Como están en el mapa, puedes procesarlos como desees o ignorarlos.

Si desea ignorar por completo los elementos de otras clases, resulta mucho más sencillo:

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 SÓLO 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());
}
Steve K avatar Feb 06 '2013 06:02 Steve K