2009-12-21 3 views
206

J'ai;Obtenir le type générique de java.util.List

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

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

+0

Pour pouvoir inspecter par programme un objet List et voir son type générique. Une méthode peut vouloir insérer des objets basés sur le type générique de la collection. Ceci est possible dans les langages qui implémentent des génériques lors de l'exécution au lieu de la compilation. –

+4

Droite - la seule façon de permettre la détection de l'exécution est de sous-classer: vous pouvez réellement étendre le type générique et ensuite utiliser la réflexion trouver la déclaration de type ce sous-type utilisé. C'est un peu de réflexion, mais possible. Malheureusement, il n'y a pas de moyen facile de faire valoir que l'on doit utiliser une sous-classe générique. – StaxMan

+1

Sûrement stringList contient des chaînes et des entiers integerList? Pourquoi le rendre plus compliqué? –

Répondre

323

Si ceux-ci sont en fait des champs d'une certaine classe, alors vous pouvez les obtenir avec un peu d'aide de 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 aussi le faire pour les types de paramètres et retourner type de méthodes. Mais s'ils sont dans la même portée de la classe/méthode que vous avez besoin de connaître, alors il est inutile de les connaître, parce que vous les avez déjà déclarés.

+0

Bien que ce soit vraiment un exercice intéressant dans les génériques, si vous avez maintenant quel domaine vous voulez, vous savez généralement de quel type il s'agit .. :) Déterminer le type d'un paramètre aurait été beaucoup plus utile, je suppose. Bon lors de la fabrication de cadres d'injection ou similaire si. +1 – falstro

+13

Il y a certainement des situations où cela est utile. Dans par exemple des frameworks ORM sans configuration. – BalusC

+3

..qui peut utiliser la classe # getDeclaredFields() pour obtenir tous les champs sans avoir besoin de connaître le nom du champ. – BalusC

15

Réponse courte: non.

Ceci est probablement un doublon, ne peut pas trouver un approprié en ce moment.

Java utilise quelque chose appelé effacement de type, ce qui signifie qu'à l'exécution les deux objets sont équivalents. Le compilateur sait que les listes contiennent des entiers ou des chaînes, et en tant que tel peut maintenir un environnement de type sécurisé. Cette information est perdue (sur une base d'instance d'objet) lors de l'exécution, et la liste contient uniquement des 'objets'.

Vous pouvez en savoir un peu plus sur la classe, de quels types elle peut être paramétrée par, mais normalement c'est juste quelque chose 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 est limité par MyClass, mais plus que cela, il n'y a aucun moyen de dire.

+0

Cela sera vrai si la classe concrète du générique n'est pas spécifiée; dans son exemple, il déclare explicitement les listes comme «Liste » et «Liste »; dans ces cas, l'effacement du type ne s'applique pas. –

1

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

Si la liste contient un nombre d'éléments différent de zéro, vous pouvez étudier le type du premier élément (en utilisant sa méthode getClass, par exemple). Cela ne vous dira 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 dans la liste.

Je ne préconiserais pas l'approche, mais dans un lien il pourrait être utile.

+0

Cela sera vrai si la classe concrète du générique n'est pas spécifiée; dans son exemple, il déclare explicitement les listes comme «Liste » et «Liste »; dans ces cas, l'effacement du type ne s'applique pas. –

2

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

Vous pourriez peut-être réfléchir sur le type déclaré du champ contenant la liste (si le type déclaré ne fait pas référence lui-même à un paramètre de type dont vous ne connaissez pas la valeur).

3

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

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

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

La méthode getGenericType() retourne un objet de type.Dans ce cas, ce sera une instance de ParametrizedType, qui à son tour a les méthodes getRawType() (qui contiendra List.class, dans ce cas) et getActualTypeArguments(), qui renverra un tableau (dans ce cas, de longueur 1, contenant String.class ou Integer.class).

+2

et ça marche pour les paramètres reçus par une méthode au lieu des champs d'une classe ??? – opensas

15

Vous pouvez faire la même chose pour les paramètres de la méthode ainsi:

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 
1
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(getFieldGenericTypefieldGenericType)); 
      } 
     } 
    } 

    //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

qu'est-ce que 'getFieldGenericTypefieldGenericType' ne peut pas être trouvé – shareef

6

Le type générique d'une collection ne devrait affaire si elle a fait des objets en elle, non? Alors, est-il pas plus facile de le 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 

Il n'y a pas une telle chose comme un type générique dans l'exécution, mais les objets à l'intérieur à l'exécution sont garantis d'être le même type que déclaré générique, il est donc assez facile juste pour tester la classe de l'élément avant de le traiter.

+5

Et si vous avez une collection vide? Ou si vous avez une collection avec un entier et une chaîne? – Falci

+0

Le contrat de la classe peut nécessiter que seules des listes d'objets finaux soient transmises et que l'appel de méthode renvoie null si la liste est vide, vide ou si le type de liste est invalide. – ggb667

+1

Dans le cas d'une collection vide, il est possible de l'affecter à n'importe quel type de liste à l'exécution, car il n'y a rien, et après l'effacement du type, une liste est une liste. Pour cette raison, je suis incapable de penser à une instance où le type d'une collection vide aura de l'importance. –

7

Si vous avez besoin pour obtenir le type générique d'un type retourné, j'ai utilisé cette approche quand je avais besoin de trouver des méthodes dans une classe qui a retourné un 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]); 
        } 
       } 
      } 
     } 

    } 

} 

Ce sorties :

Type générique est classe java.lang.String

5

l'expansion sur 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<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); 
    } 
} 
+0

Mêmes problèmes qu'avec la réponse de Steve K: Que faire si la liste est vide? Que faire si la liste est de type qui contient une chaîne et un entier? Votre code signifie que vous ne pouvez ajouter plus de chaînes si le premier élément de la liste est une chaîne, et pas d'entier du tout ... – subrunner

+0

Si la liste est vide, vous n'avez pas de chance. Si la liste est Object et qu'elle contient effectivement un Object, vous êtes OK, mais s'il y a un mélange de choses, vous n'avez pas de chance. Effacement signifie que c'est aussi bon que vous pouvez le faire. L'effacement fait défaut à mon avis.Ses ramifications sont à la fois mal comprises et insuffisantes pour répondre aux cas d'utilisation intéressants et désirés d'une préoccupation typique. Rien n'empêche de créer une classe à laquelle on peut demander de quel type il s'agit, mais ce n'est tout simplement pas le fonctionnement des classes intégrées. Les collections doivent savoir ce qu'elles contiennent, mais hélas ... – ggb667

4

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

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

eu le même problème, mais j'utilisé instanceof à la place. Est-ce que cette façon:

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 l'utilisation non vérifiées de façon que des moulages font quand vous savez qu'il est une liste, et quel type il peut être.

0

Utilisez Réflexion pour obtenir le Field pour ceux-ci alors vous pouvez simplement faire: field.genericType pour obtenir le type qui contient également les informations sur les génériques.

+0

Pensez à ajouter un exemple de code, sinon c'est plus approprié pour un commentaire et non une réponse – AlexKey

Questions connexes