web-dev-qa-db-fra.com

Obtenir le type générique de Java.util.List

J'ai;

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

Existe-t-il un moyen (facile) de récupérer le type générique de la liste?

227
Thys

Si ce sont en fait des champs d’une certaine classe, vous pouvez les obtenir avec un peu d’aide à la réflexion:

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.
    }
}

Vous pouvez également le faire pour les types de paramètres et le type de méthodes retourné.

Mais s'ils se trouvent dans le même champ d'application de la classe/méthode où vous devez les connaître, il est alors inutile de les connaître, car vous les avez déjà déclarés vous-même.

360
BalusC

Vous pouvez également faire de même pour les paramètres de méthode:

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
17
tsaixingwei

Réponse courte: non.

Ceci est probablement une copie, je ne peux pas en trouver un pour le moment.

Java utilise quelque chose appelé type effacement, ce qui signifie qu'au moment de l'exécution, les deux objets sont équivalents. Le compilateur sait que les listes contiennent des entiers ou des chaînes et peuvent donc maintenir un environnement sans risque. Ces informations sont perdues (sur une base d'objet) au moment de l'exécution et la liste ne contient que des "objets".

Vous POUVEZ en savoir un peu plus sur la classe, sur quels types elle peut être paramétrée, mais normalement, il s’agit de tout ce qui étend "Object", c’est-à-dire n'importe quoi. Si vous définissez un type comme

class <A extends MyClass> AClass {....}

AClass.class ne contiendra que le fait que le paramètre A soit lié par MyClass, mais il n’ya pas de moyen de le savoir.

17
falstro

Si vous devez obtenir le type générique d'un type renvoyé, j'ai utilisé cette approche lorsque je devais rechercher des méthodes dans une classe qui renvoyait une Collection, puis accéder à leurs types génériques:

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]);
                    }
                }
            }
        }

    }

}

Cela génère:

Le type générique est la classe Java.lang.String

9
Aram Kocharyan

Pour trouver le type générique d'un champ:

((Class)((ParameterizedType)field.getGenericType()).getActualTypeArguments()[0]).getSimpleName()
9
Mohsen Kashi

Le type générique d'une collection ne devrait avoir d'importance que si elle contient réellement des objets, n'est-ce pas? Donc, n'est-il pas plus facile de simplement faire:

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

Le type générique n'existe pas au moment de l'exécution, mais les objets qu'il contient sont assurément du même type que le générique déclaré. Il est donc assez simple de tester la classe de l'élément avant de la traiter.

7
Steve K

Développer la réponse 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);
   }
}
6
ggb667

Avait le même problème, mais j'ai utilisé instanceof à la place. Est-ce que c'est comme ça:

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");
       }
    }
}

Cela implique d’utiliser des conversions non vérifiées, alors ne le faites que lorsque vous savez que c’est une liste et quel type elle peut être. 

4
Andy

Au moment de l'exécution, non, vous ne pouvez pas.

Cependant, par réflexion, les paramètres de type sont accessibles. Essayer

for(Field field : this.getDeclaredFields()) {
    System.out.println(field.getGenericType())
}

La méthode getGenericType() renvoie un objet Type. Dans ce cas, il s'agira d'une instance de ParametrizedType, qui à son tour utilise les méthodes getRawType() (qui contiendront List.class, dans ce cas) et getActualTypeArguments(), qui renverront un tableau (dans ce cas, de longueur un, contenant soit String.class ou Integer.class ).

4
Scott Morrison

Généralement impossible, car List<String> et List<Integer> partagent la même classe d'exécution.

Vous pourrez cependant réfléchir au type déclaré du champ contenant la liste (si le type déclaré ne fait pas référence à un paramètre de type dont vous ne connaissez pas la valeur).

3
meriton

Comme d'autres l'ont dit, la seule réponse correcte est non, le type a été effacé.

Si la liste comporte un nombre d'éléments non nul, vous pouvez rechercher le type du premier élément (à l'aide de sa méthode getClass, par exemple). Cela ne vous indiquera pas le type générique de la liste, mais il serait raisonnable de supposer que le type générique était une superclasse des types de la liste.

Je ne préconiserais pas l'approche, mais dans une impasse, cela pourrait être utile.

2
Chris Arguin
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();
    }
}
1
Ashish

Utilisez Reflection pour obtenir la Field, vous pouvez simplement faire: field.genericType pour obtenir le type contenant les informations sur le générique.

0

Le type est effacé pour que vous ne puissiez pas le faire. Voir http://en.wikipedia.org/wiki/Type_erasure et http://en.wikipedia.org/wiki/Generics_in_Java#Type_erasure

0
peter.murray.rust