Pour pouvoir inspecter par programme un objet List et voir son type générique. Une méthode peut souhaiter insérer des objets en fonction du type générique de la collection. Ceci est possible dans les langages qui implémentent des génériques au moment de l'exécution au lieu de la compilation.
Steve Kuo
4
À droite - la seule façon d'autoriser la détection d'exécution est de sous-classer: vous POUVEZ réellement étendre le type générique, puis en utilisant la déclaration de type de recherche par réflexion du sous-type utilisé. C'est pas mal de réflexion, mais c'est possible. Malheureusement, il n'y a pas de moyen facile d'imposer 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 compliquer les choses?
Ben Thurley
Réponses:
408
Si ce sont en fait des champs d'une certaine classe, vous pouvez les obtenir avec un peu d'aide à la réflexion:
Vous pouvez également le faire pour les types de paramètres et renvoyer le type de méthodes.
Mais s'ils se trouvent dans le même champ d'application de la classe / méthode que vous devez les connaître, il est inutile de les connaître, car vous les avez déjà déclarés vous-même.
@Oleg Votre variable utilise <?>(type générique) au lieu de <Class>(type classe). Remplacez votre <?>par <Class>ou autre chose <E>.
BalusC
19
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
Dans method.getGenericParameterTypes (); quelle est la méthode?
Neo Ravi
18
Réponse courte: non.
Il s'agit probablement d'un doublon, impossible d'en trouver un en ce moment.
Java utilise quelque chose appelé effacement de type, 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, à ce titre, peuvent maintenir un environnement sûr de type. Ces informations sont perdues (sur la base d'une instance 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, par quels types elle pourrait être paramétrée, mais normalement c'est juste tout ce qui étend "Object", c'est-à-dire n'importe quoi. Si vous définissez un type comme
class<A extendsMyClass>AClass{....}
AClass.class contiendra uniquement le fait que le paramètre A est délimité par MyClass, mais plus que cela, il n'y a aucun moyen de le savoir.
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 List<Integer>et List<String>; dans ces cas, l'effacement de type ne s'applique pas.
Haroldo_OK
13
Le type générique d'une collection ne devrait avoir d'importance que si elle contient réellement des objets, non? N'est-il pas plus facile de faire simplement:
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 de type générique dans l'exécution, mais les objets à l'intérieur lors de l'exécution sont garantis comme étant du même type que le générique déclaré, il est donc assez facile de tester la classe de l'élément avant de le traiter.
Une autre chose que vous pouvez faire est simplement de traiter la liste pour obtenir des membres du bon type, en ignorant les autres (ou en les traitant différemment).
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());
Cela vous donnera une liste de tous les éléments dont les classes étaient des sous-classes Numberdont vous pouvez ensuite traiter selon vos besoins. Les autres éléments ont été filtrés dans d'autres listes. Parce qu'ils sont sur la carte, vous pouvez les traiter comme vous le souhaitez ou les ignorer.
Si vous voulez ignorer complètement les éléments d'autres classes, cela devient beaucoup plus simple:
Et si vous avez une collection vide? Ou si vous avez une collection <Object> avec un entier et une chaîne?
Falci
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 nulle, vide ou si le type de liste n'est pas valide.
ggb667
1
Dans le cas d'une collection vide, il est sûr de l'attribuer à n'importe quel type de liste au moment de l'exécution, car il n'y a rien dedans, et après l'effacement du type, une liste est une liste est une liste. Pour cette raison, je ne peux penser à aucun cas où le type d'une collection vide importera.
Steve K
Eh bien, cela "pourrait avoir de l'importance" si vous conserviez la référence dans un fil et la traitiez une fois que les objets ont commencé à apparaître dans un scénario de consommation de producteur. Si la liste avait le mauvais type d'objet, de mauvaises choses pourraient se produire. Je suppose que pour éviter que vous deviez arrêter le traitement en dormant jusqu'à ce qu'il y ait au moins un élément dans la liste avant de continuer.
ggb667
Si vous avez ce genre de scénario producteur / consommateur, planifier la liste pour avoir un certain type générique sans être sûr de ce qui pourrait être mis dans la liste est de toute façon une mauvaise conception. Au lieu de cela, vous le déclareriez comme une collection de Objects et lorsque vous les retirez de la liste, vous les donniez à un gestionnaire approprié en fonction de leur type.
Si vous avez besoin d'obtenir le type générique d'un type renvoyé, j'ai utilisé cette approche lorsque j'avais besoin de trouver des méthodes dans une classe qui renvoyait un Collection, puis d'accéder à leurs types génériques:
/**
* 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:protectedWhateverClass add(List<?> data){//For fluant useageif(data==null)|| data.isEmpty(){thrownewIllegalArgumentException("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);}}
Mêmes problèmes que dans la réponse de Steve K: que faire si la liste est vide? Que faire si la liste est de type <Objet> qui contient une chaîne et un entier? Votre code signifie que vous ne pouvez ajouter plus de chaînes que si le premier élément de la liste est une chaîne, et aucun entier du tout ...
subrunner
Si la liste est vide, vous n'avez pas de chance. Si la liste est Objet et qu'elle contient en fait un Objet, vous êtes OK, mais si elle contient un mélange de choses, vous n'avez pas de chance. L'effacement signifie que c'est aussi bon que possible. L'effacement fait défaut à mon avis. Les ramifications de celui-ci sont à la fois mal comprises et insuffisantes pour répondre aux cas d'utilisation intéressants et souhaités de préoccupation typique. Rien n'empêche de créer une classe à laquelle on peut demander quel type elle prend, mais ce n'est tout simplement pas ainsi que fonctionnent les classes intégrées. Les collections devraient savoir ce qu'elles contiennent, mais hélas ...
ggb667
4
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, ce sera une instance de ParametrizedType, qui à son tour a des méthodes getRawType()(qui contiendront List.class, dans ce cas) et getActualTypeArguments(), qui renverra un tableau (dans ce cas, de longueur un, contenant soit String.classou Integer.class).
J'ai eu le même problème, mais j'ai plutôt utilisé instanceof. Est-ce ainsi:
List<Object> listCheck =(List<Object>)(Object) stringList;if(!listCheck.isEmpty()){if(listCheck.get(0)instanceofString){System.out.println("List type is String");}if(listCheck.get(0)instanceofInteger){System.out.println("List type is Integer");}}}
Cela implique l'utilisation de transtypages non contrôlés, donc ne faites cela que lorsque vous savez qu'il s'agit d'une liste et de quel type elle peut être.
Généralement impossible, car List<String>et List<Integer>partager la même classe d'exécution.
Vous pourrez peut-être réfléchir au type déclaré du champ contenant la liste (si le type déclaré ne fait pas lui-même référence à un paramètre de type dont vous ne connaissez pas la valeur).
Comme d'autres l'ont dit, la seule bonne 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 rechercher 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 de la liste.
Je ne préconiserais pas l'approche, mais dans une impasse, cela pourrait être utile.
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 List<Integer>et List<String>; dans ces cas, l'effacement de type ne s'applique pas.
Haroldo_OK
2
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;publicclassGenericTypeOfCollectionTest{publicclassFormBean{}publicclassMyClazz{privateList<FormBean> list =newArrayList<FormBean>();}@Testpublicvoid testName()throwsException{Field[] fields =MyClazz.class.getFields();for(Field field : fields){//1. Check if field is of Collection Typeif(Collection.class.isAssignableFrom(field.getType())){//2. Get Generic type of your fieldClass fieldGenericType = getFieldGenericType(field);//3. Compare with <FromBean>Assert.assertTrue("List<FormBean>",FormBean.class.isAssignableFrom(fieldGenericType));}}}//Returns generic type of any fieldpublicClass 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 & FormBeanreturnnewBoolean(false).getClass();}}
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 List<Integer>et List<String>; dans ces cas, l'effacement de type ne s'applique pas.
Haroldo_OK
0
Utilisez Reflection pour obtenir le Fieldpour ceux-ci, alors vous pouvez simplement faire: field.genericTypepour obtenir le type qui contient également les informations sur les génériques.
Réponses:
Si ce sont en fait des champs d'une certaine classe, vous pouvez les obtenir avec un peu d'aide à la réflexion:
Vous pouvez également le faire pour les types de paramètres et renvoyer le type de méthodes.
Mais s'ils se trouvent dans le même champ d'application de la classe / méthode que vous devez les connaître, il est inutile de les connaître, car vous les avez déjà déclarés vous-même.
la source
<?>
(type générique) au lieu de<Class>
(type classe). Remplacez votre<?>
par<Class>
ou autre chose<E>
.Vous pouvez également faire de même pour les paramètres de méthode:
la source
Réponse courte: non.
Il s'agit probablement d'un doublon, impossible d'en trouver un en ce moment.
Java utilise quelque chose appelé effacement de type, 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, à ce titre, peuvent maintenir un environnement sûr de type. Ces informations sont perdues (sur la base d'une instance 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, par quels types elle pourrait être paramétrée, mais normalement c'est juste tout ce qui étend "Object", c'est-à-dire n'importe quoi. Si vous définissez un type comme
AClass.class contiendra uniquement le fait que le paramètre A est délimité par MyClass, mais plus que cela, il n'y a aucun moyen de le savoir.
la source
List<Integer>
etList<String>
; dans ces cas, l'effacement de type ne s'applique pas.Le type générique d'une collection ne devrait avoir d'importance que si elle contient réellement des objets, non? N'est-il pas plus facile de faire simplement:
Il n'y a pas de type générique dans l'exécution, mais les objets à l'intérieur lors de l'exécution sont garantis comme étant du même type que le générique déclaré, il est donc assez facile de tester la classe de l'élément avant de le traiter.
Une autre chose que vous pouvez faire est simplement de traiter la liste pour obtenir des membres du bon type, en ignorant les autres (ou en les traitant différemment).
Cela vous donnera une liste de tous les éléments dont les classes étaient des sous-classes
Number
dont vous pouvez ensuite traiter selon vos besoins. Les autres éléments ont été filtrés dans d'autres listes. Parce qu'ils sont sur la carte, vous pouvez les traiter comme vous le souhaitez ou les ignorer.Si vous voulez ignorer complètement les éléments d'autres classes, cela devient beaucoup plus simple:
Vous pouvez même créer une méthode utilitaire pour vous assurer qu'une liste contient UNIQUEMENT les éléments correspondant à une classe spécifique:
la source
Object
s et lorsque vous les retirez de la liste, vous les donniez à un gestionnaire approprié en fonction de leur type.Pour rechercher le type générique d'un champ:
la source
Si vous avez besoin d'obtenir le type générique d'un type renvoyé, j'ai utilisé cette approche lorsque j'avais besoin de trouver des méthodes dans une classe qui renvoyait un
Collection
, puis d'accéder à leurs types génériques:Cela produit:
la source
Développant la réponse de Steve K:
la source
Au moment de l'exécution, non, vous ne pouvez pas.
Cependant, par réflexion, les paramètres de type sont accessibles. Essayer
La méthode
getGenericType()
renvoie un objet Type. Dans ce cas, ce sera une instance deParametrizedType
, qui à son tour a des méthodesgetRawType()
(qui contiendrontList.class
, dans ce cas) etgetActualTypeArguments()
, qui renverra un tableau (dans ce cas, de longueur un, contenant soitString.class
ouInteger.class
).la source
method.getGenericParameterTypes()
pour obtenir les types déclarés des paramètres d'une méthode.J'ai eu le même problème, mais j'ai plutôt utilisé instanceof. Est-ce ainsi:
Cela implique l'utilisation de transtypages non contrôlés, donc ne faites cela que lorsque vous savez qu'il s'agit d'une liste et de quel type elle peut être.
la source
Généralement impossible, car
List<String>
etList<Integer>
partager la même classe d'exécution.Vous pourrez peut-être réfléchir au type déclaré du champ contenant la liste (si le type déclaré ne fait pas lui-même référence à un paramètre de type dont vous ne connaissez pas la valeur).
la source
Comme d'autres l'ont dit, la seule bonne 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 rechercher 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 de la liste.
Je ne préconiserais pas l'approche, mais dans une impasse, cela pourrait être utile.
la source
List<Integer>
etList<String>
; dans ces cas, l'effacement de type ne s'applique pas.la source
getFieldGenericTypefieldGenericType
cela ne peut pas être trouvéLe type est effacé, vous ne pourrez donc pas. Voir http://en.wikipedia.org/wiki/Type_erasure et http://en.wikipedia.org/wiki/Generics_in_Java#Type_erasure
la source
List<Integer>
etList<String>
; dans ces cas, l'effacement de type ne s'applique pas.Utilisez Reflection 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.la source