Les génériques sont l'une des caractéristiques les plus controversées du langage Java. Ils permetent à un type ou à une méthode de fonctionner sur des objets de différents types tout en offrant une sécurité de type à la compilation, faisant de Java un langage entièrement typé statiquement. Dans cet article, je vais parler de cinq choses que tous les développeurs Java devraient connaître les génériques.
Les génériques sont implémentés à l'aide de l'effacement de type
En Java, une classe ou une interface peut être déclarée pour définir un ou plusieurs paramètres de type et ces paramètres de type doivent être fournis au moment de la construction de l'objet. Par exemple:
list.add(Long.valueOf(1));
list.add(Long.valueOf(2));
Dans l'exemple ci-dessus, une liste est créée qui ne peut contenir que des éléments de type Long et si vous essayez d'ajouter un autre type d'élément à cette liste, cela vous donnera une erreur de compilation. ça aide à détecter les erreurs au moment de la compilation et sécurise votre code. Une fois cette partie de code compilée, les informations de type sont effacées, résultant en un code octet similaire à celui que nous aurions si le même morceau de code était écrit en utilisant Java 1.4 et moins. Cela entraîne une compatibilité binaire entre différentes versions de Java. Ainsi, une List ou une List<> sont toutes représentées à l'exécution par le même type, List.
Les génériques ne supportent pas le sous-typage
Un générique ne prend pas en charge le sous-typage, ce qui signifie que List n'est pas considéré comme un sous-type de List, où S est un sous-type de T. Par exemple:
Le morceau de code montré ci-dessus ne compilera pas parce que s'il compile que la sécurité de type ne peut pas être accomplie. Pour rendre ceci plus clair, prenons le morceau de code suivant montré ci-dessous où à la ligne 4 nous assignons une liste de longue à une liste de nombres. Ce morceau de code ne compile pas car s'il avait pu être compilé, nous pourrions ajouter une double valeur dans une liste de longs. Cela aurait pu entraîner ClassCastException à l'exécution et la sécurité du type n'a pas pu être atteinte.
list.add(Long.valueOf(1));
list.add(Long.valueOf(2));
List<Number> numbers = list;
Vous ne pouvez pas créer de tableaux génériques
Vous ne pouvez pas créer de tableaux génériques comme indiqué ci-dessous, car les tableaux contiennent des informations de type à l'exécution sur le type d'éléments. Les tableaux utilisent ces informations au moment de l'exécution pour vérifier le type de l'objet qu'il contient et lancera ArrayStoreException si le type ne correspond pas. Mais avec les génériques, les informations de type sont effacées et la vérification de tableaux réussit alors qu'elle devait échouer.
Vous ne pouvez même pas créer de tableaux de classes génériques d'interfaces. Par exemple, le code indiqué ci-dessous ne compile pas.
Les tableaux se comportent différemment des collections car les tableaux sont covariants par défaut, ce qui signifie que S [] est un sous-type de T [] quand S est un sous-type de T, et donc les génériques ne supporte pas la covariance. Ainsi, si le code ci-dessus avait été compilé, la vérification du tableaux réussirait alors qu'elle devait échouer. Par exemple,
Si les tableaux génériques étaient autorisés, nous pourrions assigner ints array à un tableau d'objets car les tableaux sont covariants. Après cela, nous pourrions ajouter une liste de double au tableau obj. Nous nous attendons à ce que cela échoue avec ArrayStoreException car nous affectons List of double à un tableau de Liste d'entiers. Mais la machine virtuelle Java ne peut pas détecter une incompatibilité de type car les informations de type sont effacées. Donc la vérification du tableaux réussit, bien qu'il aurait dû échouer.
L'utilisation de caractères génériques avec extend ou super pour augmenter la flexibilité de l'API
Il y a des moments où vous devez travailler non seulement avec T, mais aussi avec des sous-types de T. Par exemple, la méthode addAll dans l'interface Collection qui ajoute tous les éléments de la collection spécifiée à la collection sur laquelle elle est appelée. La méthode addAll a la signature suivante:
Ce ? extends E s'assure que vous pouvez non seulement ajouter une collection de type E mais aussi un sous-type de E.? est appelé le joker et? est dit être borné par E. Donc, si nous créons une liste de nombre alors nous pouvons non seulement ajouter la liste des nombres mais nous pouvons également ajouter la liste des entiers ou tout autre sous-type de nombre.
ArrayList<Integer> integers = new ArrayList<Integer>();
ArrayList<Long> longs = new ArrayList<Long>();
ArrayList<Float> floats = new ArrayList<Float>();
numbers.addAll(integers);
Jusqu'à présent, nous avons couvert l'utilisation de extends avec des caractères génériques et nous avons vu que l'API est devenue plus flexible après l'utilisation de extends. Mais où allons-nous utiliser super? La classe Collections a une méthode appelée addAll qui ajoute tous les éléments spécifiés à la collection spécifiée. Il a la signature suivante
Dans cette méthode, vous ajoutez des éléments de type T à la collection c. super est utilisé à la place de extends parce que les éléments sont ajoutés dans la collection c alors que dans l'exemple précédent de l'interface Collection les éléments de la méthode addAll ont été lus dans la collection. Dans le livre Java efficace, Joshua Bloch appelle ceci par PECS.
PECS signifie Producteur Extends, Consommateur Super. Il s'avère très utile chaque fois que vous êtes confus quant à savoir si vous devez utiliser étend ou super.
L'utilisation de plusieurs bornes
Les limites multiples sont l'une des caractéristiques génériques que la plupart des développeurs ne connaissent pas. Il permet à une variable de type ou à un caractère générique d'avoir plusieurs limites. Par exemple, si vous définissez une contrainte telle que celle-ci, le type doit être un nombre et implémenter Comparable.
int compareNumbers(T t1, T t2){return t1.compareTo(t2);}
Il s'assure que vous ne pouvez comparer que deux nombres qui implémentent Comparable. Les limites multiples suivent les mêmes contraintes que celles suivies par une classe, c'est-à-dire que T ne peut pas étendre deux classes, vous devez d'abord spécifier la classe puis l'interface, et T peut étendre n'importe quel nombre d'interfaces.
Aucun commentaire