• Home
  • Docker
  • Kubernetes
  • LLMs
  • Java
  • Ubuntu
  • Maven
  • Big Data
  • Archived
Java | Généricité (generics)
  1. Introduction
  2. Classes génériques
  3. Utiliser le type générique
  4. Wildcard (?)
    1. Utiliser le wildcard comme sous-type du type générique
    2. Utiliser le wildcard comme super-type du type générique
    3. Utiliser le wildcard pour représenter n'importe quel type
  5. Méthodes génériques
  6. Constructeurs génériques
  7. Lancer des exceptions génériques

  1. Introduction
    La généricité offre la capacité de spécifier des types génériques pour les variables (incluant le type de retour des méthodes et les paramètres des méthodes et des constructeurs).

    Le type générique est utilisé uniquement à la compilation ; le compilateur s'en sert pour vérifier que le type est correct lors de l'instanciation des classes et l'invocation des méthodes.

    L'information sur le type générique est complètement perdue dans le bytecode.
  2. Classes génériques
    Pour définir un type générique pour une classe il faut suivre son nom par un identifiant qui doit etre placé entre les caractères "<" et ">".

    Il est possible de spécifier plusieurs types génériques ; les noms des types génériques doivent être placés l'un à coté de l'autre séparés par le caractère "," : <T,R,P>.

    Typiquement les noms des types génériques doivent être : T, E, R, P, …

    Le nom du type générique peut être utilisé comme :
    • type des attributs et des variables ;

    • type de retour des méthodes ;

    • type des paramètres des méthodes ;

    • ou type des paramètres des constructeurs.


    Comme mentionné précédemment le type générique est présent uniquement dans le code source et il est complètement perdu dans le bytecode. En fait, le code précédent est considéré, lors de la génération du bytecode, comme s'il a été écrit comme suit :


    Le compilateur remplace le type générique par le type de la classe Object.
  3. Utiliser le type générique
    Au niveau de la définition des classes génériques, le type générique n'est pas très impressionnant, surtout en connaissant que cette information est présente uniquement dans le code source et elle est perdue une fois le bytecode est généré.

    Mais, cette impression va changer en commençant à utiliser le type générique pour déclarer/instancier une classe générique et invoquer ses méthodes génériques ; le compilateur s'en sert pour contrôler et vérifier que le type spécifié lors de l'instanciation est correct partout dans le code où les méthodes génériques de cette classe sont invoquées.

    Le type que l'on souhaite utilisé doit être spécifié lors de la déclaration des variables qui référencent la classe générique.
    La déclaration est faite de la même façon que la définition de la classe générique : le type doit être placé entre les caractères "<" et ">".

    Encore une fois, le type générique est présent uniquement dans le code source et il est perdu dans le bytecode :

    Le code précédent est considéré, lors de la génération du bytecode, comme s'il a été écrit comme suit :

    Le compilateur utilise le type générique pour ajouter un cast lors de l'affectation d'une référence de type générique à une variable du type spécifié lors de la déclaration de la classe.

    Lors de la génération du bytecode, le code suivant Number v1 = myGenericClass.getAttr1(); sera remplacé par Number v1 = (Number) myGenericClass.getAttr1();.

    Notes :
    • Le type spécifié lors de l'instanciation d'une classe doit être exactement le même type utilisé pour la déclaration de la variable qui référence cette classe. Le compilateur générera une erreur si le type est différent (un sous type est considéré différent) :

      Cette restriction s'applique aussi aux valeurs de retour et arguments des méthodes :

      La règle générale est la suivante : le compilateur va toujours interdire l'affectation d'une référence à une autre si les types génériques des deux références ne correspondent pas exactement.

    • La règle précédente s'applique uniquement pour l'instanciation des classes et la déclaration des variables et des paramètres qui utilisent explicitement le type générique.

      Si l'instanciation des classes et la déclaration des variables et des paramètres n'utilisent pas le type générique, alors le compilateur ne ferra aucune vérification et ne générera aucune erreur. Cependant, il affichera un message d'avertissement :

    • Le type spécifié lors de l'invocation des méthodes génériques de la classe peut être le même type ou un sous-type du type spécifié lors de l'instanciation de cette classe :

      Le compilateur affichera une erreur si le type spécifié lors de l'invocation des méthodes génériques de la classe est différent du type spécifié lors de l'instanciation de cette classe :
  4. Wildcard (?)
    Le wildcard (le caractère "?") est utilisé pour préciser que le type générique peut être un sous-type ou un super-type d'un certain type.
    Il peut être aussi utilisé pour préciser que le type générique peut être n'importe quel type !

    Attention, le wildcard peut être utilisé uniquement à la déclaration des variables (incluant les paramètres des méthodes et les types de retour des méthodes) :

    1. Utiliser le wildcard comme sous-type du type générique
      Dans l'exemple suivant, on peut assigner à la variable myGenericClass n'importe quelle référence dont le type générique est le même type ou un sous-type de la classe Number.


      Il y a cependant quelques restrictions avec l'utilisation de cette syntaxe :
      • Vous ne pouvez pas utiliser la référence créée avec cette syntaxe pour modifier les attributs dont le type est générique (typiquement en accédant directement à ces attributs ou en invoquant des méthodes qui modifient ces attributs).

      • Vous pouvez utiliser la référence créée avec cette syntaxe, seulement, pour lire les attributs dont le type est générique (typiquement en accédant directement à ces attributs ou en invoquant des méthodes qui renvoient ces attributs), mais il faut ajouter un cast explicite pour pouvoir le faire :

        Attention, le compilateur vérifie uniquement que le type utilisé pour le cast est un sous-type du type générique défini à la déclaration de la variable. Ce qui veut dire que vous pouvez avoir une erreur à l'exécution si le cast n'est pas possible :

        Résultat d'exécution du code :

    2. Utiliser le wildcard comme super-type du type générique
      On peut assigner à la variable n'importe quelle référence dont le type générique est le même type ou un super-type de la classe Number.


      Vous pouvez utiliser la référence créée avec cette syntaxe pour lire et modifier les attributs dont le type est générique (typiquement en accédant directement à ces attributs ou en invoquant des méthodes qui renvoient ou modifient ces attributs), mais il faut ajouter un cast explicite pour pouvoir le faire :

      Attention, le compilateur vérifie uniquement que le type utilisé pour le cast est un sous-type du type générique défini à la déclaration de la variable. Ce qui veut dire que vous pouvez avoir une erreur à l'exécution si le cast n'est pas possible :

      Résultat d'exécution du code :
    3. Utiliser le wildcard pour représenter n'importe quel type
      Dans l'exemple suivant, on peut assigner à la variable myGenericClass n'importe quellle référence dont le type générique peut être n'importe quel type.


      En fait, la syntaxe <?> est équivalente à <? extends Object>.
      Ce qui veut dire que cette syntaxe a les mêmes restrictions (voir ci-dessus) de l'utilisation du wildcard comme sous-type du type générique:

      • Vous ne pouvez pas utiliser la référence créée avec cette syntaxe pour modifier les attributs dont le type est générique (typiquement en accédant directement à ces attributs ou en invoquant des méthodes qui modifient ces attributs).

      • Vous pouvez utiliser la référence créée avec cette syntaxe, seulement, pour lire les attributs dont le type est générique (typiquement en accédant directement à ces attributs ou en invoquant des méthodes qui renvoient ces attributs), mais il faut ajouter un cast explicite pour pouvoir le faire :

        Attention, le compilateur vérifie uniquement que le type utilisé pour le cast est correct pour l'instruction d'affectation.
        Ce qui veut dire que vous pouvez avoir une erreur à l'exécution si le cast n'est pas possible :

        Résultat d'exécution du code :
  5. Méthodes génériques
    Il est possible de définir des types génériques au niveau des méthodes.
    La syntaxe de la définition ainsi que l'utilisation du type générique restent semblables à ce que nous avons vu ci-dessus.
    La seule particularité est que la définition du type générique doit être faite juste avant le type du retour de la méthode.

    Le type générique peut être spécifié à la fois pour le type de retour de la méthode, ses paramètres, et ses variables locales.

  6. Constructeurs génériques
    Les constructeurs peuvent aussi définir des types génériques ; la syntaxe est la même que celle utilisée pour les méthodes.
  7. Lancer des exceptions génériques
    Pour lancer des exceptions génériques, on utilise la même syntaxe pour définir des types génériques pour les méthodes.
    On utilise le mot clé throws pour lancer l'exception spécifiée par le type générique.
    Il faut que la définition du type générique précise que le type est un sous-type des classes Throwable, Error, Exception, ou n'importe quelle classe qui hérite de ces classes.

    Résultat d'exécution du code :
© 2025  mtitek