224 votes

Java : comment obtenir un littéral de classe à partir d'un type générique ?

Typiquement, j'ai vu des gens utiliser la classe littérale comme ceci :

Class<Foo> cls = Foo.class;

Mais que faire si le type est générique, par exemple List ? Cela fonctionne bien, mais il y a un avertissement puisque List doit être paramétré :

Class<List> cls = List.class

Alors pourquoi ne pas ajouter un <?> ? Eh bien, cela provoque une erreur de correspondance de type :

Class<List<?>> cls = List.class

Je pensais que quelque chose comme ça fonctionnerait, mais c'est juste une erreur de syntaxe :

Class<List<Foo>> cls = List<Foo>.class

Comment puis-je obtenir un Class<List<Foo>> de manière statique, par exemple en utilisant le littéral de la classe ?

I podría utiliser @SuppressWarnings("unchecked") pour se débarrasser des avertissements causés par l'utilisation non paramétrée de List dans le premier exemple, Class<List> cls = List.class mais je ne préfère pas.

Des suggestions ?

174voto

cletus Points 276888

Vous ne pouvez pas en raison de effacement de type .

Les génériques Java ne sont guère plus que du sucre syntaxique pour les casts d'objets. Pour le démontrer :

List<Integer> list1 = new ArrayList<Integer>();
List<String> list2 = (List<String>)list1;
list2.add("foo"); // perfectly legal

Le seul cas où les informations sur les types génériques sont conservées au moment de l'exécution est celui de l'application [Field.getGenericType()](http://docs.oracle.com/javase/6/docs/api/java/lang/reflect/Field.html#getGenericType()) si l'on interroge les membres d'une classe par réflexion.

Tout ceci est la raison pour laquelle [Object.getClass()](http://docs.oracle.com/javase/6/docs/api/java/lang/Object.html#getClass()) a cette signature :

public final native Class<?> getClass();

La partie importante étant Class<?> .

Pour le dire autrement, du FAQ sur les génériques Java :

Pourquoi n'y a-t-il pas de littéral de classe pour les types paramétrés concrets ?

Parce que le type paramétré n'a pas de représentation exacte du type d'exécution.

Un littéral de classe désigne une Class qui représente un type donné. Par exemple, la classe littérale String.class désigne le Class qui représente le type String et est identique à la Class qui est retourné lorsque méthode getClass est invoqué sur un String objet. Un littéral de classe peut être utilisé pour les contrôles de type à l'exécution et pour la réflexion.

Les types paramétrés perdent leur type lorsqu'ils sont traduits en code d'octet pendant la compilation dans un processus appelé effacement de type . En tant qu'effet effet secondaire de l'effacement de type, toutes instanciations d'un type générique partagent la même représentation à l'exécution, à savoir celle du type brut correspondant. En d'autres termes, les types paramétrés n'ont pas de représentation de type qui leur est propre. Par conséquent, il n'y a inutile de former des littéraux de classe tels que List<String>.class , List<Long>.class y List<?>.class puisque aucun Class les objets existent. Seul le type brut List a un Class qui représente son temps d'exécution d'exécution. Il est désigné sous le nom de List.class .

1 votes

Aha ! Cela a plus de sens. Je pensais que la classe littérale sur un générique n'aurait pas beaucoup de sens, mais je n'avais aucune idée que c'était dû à cela. Merci !

15 votes

List<Integer> list1 = new ArrayList<Integer>(); List<String> list2 = (List<String>)list1; list2.add("foo"); // perfectly legal Vous ne pouvez pas faire cela en Java, vous obtenez une erreur de compilation de type mismatch !

5 votes

Alors... que dois-je faire si j'en ai besoin ?

71voto

Santi P. Points 71

Il n'existe pas de littéraux de classe pour les types paramétrés, mais il existe des objets Type qui définissent correctement ces types.

Voir java.lang.reflect.ParameterizedType - http://java.sun.com/j2se/1.5.0/docs/api/java/lang/reflect/ParameterizedType.html

La bibliothèque Gson de Google définit une classe TypeToken qui permet de générer simplement des types paramétrés et l'utilise pour spécifier des objets json avec des types paramétrés complexes d'une manière conviviale et générique. Dans votre exemple, vous utiliseriez :

Type typeOfListOfFoo = new TypeToken<List<Foo>>(){}.getType()

J'avais l'intention de poster des liens vers la javadoc des classes TypeToken et Gson mais Stack Overflow ne me laisse pas poster plus d'un lien puisque je suis un nouvel utilisateur, vous pouvez facilement les trouver en utilisant la recherche Google

2 votes

Avec cela, j'ai pu créer une classe avec un E générique et ensuite utiliser clzz = new TypeToken<E>(){}.getRawType(); pour itérer plus tard sur des enums structurés de façon similaire avec clzz.getEnumConstants() et enfin utiliser la réfection pour appeler les méthodes membres à la manière de Method method = clzz.getDeclaredMethod("getSomeFoo"); tant de victoire ! Merci !

70voto

slaurent Points 111

Vous pouvez le faire avec un double moulage :

@SuppressWarnings("unchecked") Class<List<Foo>> cls = (Class<List<Foo>>)(Object)List.class

9voto

Jim Garrison Points 39523

Pour développer la réponse de Cletus, au moment de l'exécution, tous les enregistrements des types génériques sont supprimés. Les génériques ne sont traités que dans le compilateur et sont utilisés pour fournir une sécurité de type supplémentaire. Ils ne sont en fait qu'un raccourci qui permet au compilateur d'insérer des analyses de type aux endroits appropriés. Par exemple, auparavant, vous auriez dû faire ce qui suit :

List x = new ArrayList();
x.add(new SomeClass());
Iterator i = x.iterator();
SomeClass z = (SomeClass) i.next();

devient

List<SomeClass> x = new ArrayList<SomeClass>();
x.add(new SomeClass());
Iterator<SomeClass> i = x.iterator();
SomeClass z = i.next();

Cela permet au compilateur de vérifier votre code au moment de la compilation, mais au moment de l'exécution, il ressemble toujours au premier exemple.

0 votes

Merci pour cette explication supplémentaire - ma compréhension des génériques est beaucoup plus claire maintenant que je réalise qu'ils ne sont pas un mécanisme d'exécution :).

2 votes

À mon avis, cela signifie simplement que les génériques ont été implémentés de manière médiocre par Sun, espérons qu'Oracle corrige cela un jour. L'implémentation des génériques en C# est bien meilleure (Anders est divin).

1 votes

@MarcelValdezOrozco AFAIK, en Java, ils l'ont implémenté de cette façon parce qu'ils voulaient que l'ancien code (pré-1.5) fonctionne sur les nouvelles JVM sans aucun problème. Il semble que ce soit une décision de conception très intelligente qui se soucie de la compatibilité. Je ne pense pas qu'il y ait quoi que ce soit de médiocre là-dedans.

4voto

aventurin Points 601

Vous pourriez utiliser une méthode d'aide pour vous débarrasser de @SuppressWarnings("unchecked") dans toute une classe.

@SuppressWarnings("unchecked")
private static <T> Class<T> generify(Class<?> cls) {
    return (Class<T>)cls;
}

Vous pourriez alors écrire

Class<List<Foo>> cls = generify(List.class);

D'autres exemples d'utilisation sont

  Class<Map<String, Integer>> cls;

  cls = generify(Map.class);

  cls = TheClass.<Map<String, Integer>>generify(Map.class);

  funWithTypeParam(generify(Map.class));

public void funWithTypeParam(Class<Map<String, Integer>> cls) {
}

Cependant, étant donné que cette méthode est rarement utile et que son utilisation va à l'encontre de la vérification de type du compilateur, je ne recommande pas de l'implémenter à un endroit où elle est accessible au public.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X