Jetons de type Super dans les génériques Java

1. Vue d'ensemble

Dans ce didacticiel, nous allons nous familiariser avec les jetons de type super et voir comment ils peuvent nous aider à conserver les informations de type générique au moment de l'exécution.

2. L'effacement

Parfois, nous devons transmettre des informations de type particulier à une méthode . Par exemple, ici, nous attendons de Jackson qu'il convertisse le tableau d'octets JSON en String:

byte[] data = // fetch json from somewhere String json = objectMapper.readValue(data, String.class);

Nous communiquons cette attente via un jeton de classe littéral, dans ce cas, le String.class.

Cependant, nous ne pouvons pas définir la même attente pour les types génériques aussi facilement:

Map json = objectMapper.readValue(data, Map.class); // won't compile

Java efface les informations de type générique lors de la compilation. Par conséquent, les paramètres de type générique ne sont qu'un artefact du code source et seront absents lors de l'exécution.

2.1. Réification

Techniquement parlant, les types génériques ne sont pas réifiés en Java. Dans la terminologie du langage de programmation, lorsqu'un type est présent au moment de l'exécution, nous disons que ce type est réifié.

Les types réifiés en Java sont les suivants:

  • Types primitifs simples tels que long
  • Abstractions non génériques telles que String ou Runnable
  • Types bruts tels que List ou HashMap
  • Types génériques dans lesquels tous les types sont des caractères génériques illimités tels que List ou HashMap
  • Tableaux d'autres types réifiés tels que String [], int [], List [] ou Map []

Par conséquent, nous ne pouvons pas utiliser quelque chose comme Map.class car la carte n'est pas un type réifié.

3. Jeton de type super

En fin de compte, nous pouvons tirer parti de la puissance des classes internes anonymes en Java pour préserver les informations de type pendant la compilation:

public abstract class TypeReference { private final Type type; public TypeReference() { Type superclass = getClass().getGenericSuperclass(); type = ((ParameterizedType) superclass).getActualTypeArguments()[0]; } public Type getType() { return type; } }

Cette classe est abstraite, nous ne pouvons donc en dériver que des sous-classes.

Par exemple, nous pouvons créer un interne anonyme:

TypeReference token = new TypeReference() {};

Le constructeur effectue les étapes suivantes pour conserver les informations de type:

  • Tout d'abord, il obtient les métadonnées de la superclasse générique pour cette instance particulière - dans ce cas, la superclasse générique est TypeReference
  • Ensuite, il obtient et stocke le paramètre de type réel pour la superclasse générique - dans ce cas, ce serait Map

Cette approche pour préserver les informations de type générique est généralement connue sous le nom de jeton de super type :

TypeReference token = new TypeReference() {}; Type type = token.getType(); assertEquals("java.util.Map", type.getTypeName()); Type[] typeArguments = ((ParameterizedType) type).getActualTypeArguments(); assertEquals("java.lang.String", typeArguments[0].getTypeName()); assertEquals("java.lang.Integer", typeArguments[1].getTypeName());

En utilisant des jetons de super type, nous savons que le type de conteneur est Map et que ses paramètres de type sont String et Integer.

Ce modèle est si célèbre que des bibliothèques comme Jackson et des frameworks comme Spring en ont leurs propres implémentations. L'analyse d'un objet JSON dans une carte peut être effectuée en définissant ce type avec un jeton de super type:

TypeReference token = new TypeReference() {}; Map json = objectMapper.readValue(data, token);

4. Conclusion

Dans ce didacticiel, nous avons appris comment utiliser des jetons de super type pour conserver les informations de type générique au moment de l'exécution.

Comme d'habitude, tous les exemples sont disponibles sur over sur GitHub.