Article de reference

Génériques en Java

Les génériques sont une fonctionnalité de la programmation générique introduite dans le langage Java en 2004 avec la version J2SE 5.0. Ils ont été conçus pour étendre le système...

Les génériques sont une fonctionnalité de la programmation générique introduite dans le langage Java en 2004 avec la version J2SE 5.0. Ils ont été conçus pour étendre le système de types de Java afin de permettre à un type ou une méthode d'opérer sur des objets de types variés tout en garantissant la sécurité des types à la compilation. Cette sécurité des types à la compilation exige que les fonctions paramétriques polymorphes ne soient pas implémentées dans la machine virtuelle Java , car la sécurité des types est impossible dans ce cas.

Le framework de collections Java prend en charge les génériques pour spécifier le type des objets stockés dans une instance de collection.

En 1998, Gilad Bracha , Martin Odersky , David Stoutamire et Philip Wadler ont créé Generic Java, une extension du langage Java pour prendre en charge les types génériques. Generic Java a été intégré à Java avec l'ajout de caractères génériques .

variable de type est un identificateur non qualifié. Les variables de type sont introduites par les déclarations de classes génériques, les déclarations d'interfaces génériques, les déclarations de méthodes génériques et les déclarations de constructeurs génériques. Elles sont souvent composées d'une seule lettre majuscule, mais celles qui comportent plus d'une lettre sont généralement écrites en MAJUSCULES (contrairement à d'autres langages comme C++ , C# et Rust où elles sont en PascalCase ), par exemple . interface est générique si elle déclare une ou plusieurs variables de type. Elle définit une ou plusieurs variables de type qui servent de paramètres. Une déclaration d'interface générique définit un ensemble de types, un pour chaque appel possible de la section des paramètres de type. Tous les types paramétrés partagent la même interface à l'exécution.
  • Une méthode est générique si elle déclare une ou plusieurs variables de type. Ces variables de type sont appelées paramètres de type formels de la méthode. La liste des paramètres de type formels se présente sous la même forme que celle des paramètres de type d'une classe ou d'une interface.
  • Un constructeur peut être déclaré générique, indépendamment du fait que la classe dans laquelle il est déclaré soit elle-même générique ou non. Un constructeur est générique s'il déclare une ou plusieurs variables de type. Ces variables de type sont appelées paramètres de type formels du constructeur. La forme de la liste des paramètres de type formels est identique à celle de la liste des paramètres de type d'une classe ou d'une interface générique.
  • Motivation

    Le bloc de code Java suivant illustre un problème qui survient lorsqu'on n'utilise pas les génériques. Il déclare d'abord un objet entier .

    test" ); // Une chaîne de caractères qui ne peut pas être convertie en entier Integer i = ( Integer ) v.get ( 0 ) ; // Erreur d'exécution

    Ceci est similaire au C , qui ne dispose pas de génériques, et dont les structures de données de type collection stockent généralement les données sous forme de void*(un pointeur void ), à partir duquel les objets doivent être convertis explicitement.

    Bien que le code soit compilé sans erreur, il lève une exception d'exécution java.lang.ClassCastExceptionlors de l'exécution de la troisième ligne. Ce type d' erreur logique peut être détecté à la compilation grâce aux génériques , ce qui constitue la principale motivation de leur utilisation. Il définit une ou plusieurs variables de type qui servent de paramètres.

    Le fragment de code ci-dessus peut être réécrit en utilisant des génériques comme suit :

    test" ); Integer i = ( Integer ) v.get ( 0 ) ; // ( erreur de type) erreur de compilation

    Le paramètre de type java.lang.Stringentre chevrons déclare que l' java.util.ArrayListobjet est constitué de java.lang.String(un descendant des constituants java.util.ArrayListgénériques de l'objet java.lang.Object). Grâce aux génériques, il n'est plus nécessaire de convertir la troisième ligne vers un type particulier, car le résultat v.get(0)est défini comme étant de type générique java.lang.Stringpar le code généré par le compilateur .

    L’erreur logique présente dans la troisième ligne de ce fragment sera détectée comme une erreur de compilation (avec J2SE 5.0 ou version ultérieure) car le compilateur détectera que v.get(0)`returns` java.lang.Stringest différent de `true` java.lang.Integer. Pour un exemple plus détaillé, voir la référence.

    Voici un petit extrait de la définition des interfaces carte :

    (%s, %s)" , key , value ) ; } }

    Cette classe générique pourrait être utilisée de différentes manières, par exemple :

    , "A" ) ; Entry < String , Integer > note = new Entry < String , Integer > ( " Mike" , 100 ); System.out.printf ( " note : %s%n" , note); System.out.printf ( " note : % s % n" , note ) ;Entry < Integer , Boolean > prime = new Entry < > ( 13 , true ); if ( prime.getValue ( ) ) { System.out.println ( " % s est premier.%n" , prime.getKey ( ) ); } else { System.out.println ( " % s n'est pas premier.%n" , prime.getKey ( ) ) ; }

    Il affiche :

    une erreur de compilation (symbole introuvable " T"), car il représente la déclaration du symbole.

    Dans de nombreux cas, l'utilisateur de la méthode n'a pas besoin d'indiquer les paramètres de type, car ils peuvent être déduits :

    );

    Les paramètres peuvent être ajoutés explicitement si nécessaire :

    );

    L'utilisation de types primitifs est interdite ; il est nécessaire d'utiliser des versions encapsulées . Seuls les types référence peuvent être stockés dans un paramètre de type générique. Par exemple, la compilation échouera ; il faudrait plutôt utiliser les classes enveloppes pour les types primitifs (comme `int` ).l'inférence de type , Java SE 7 et versions ultérieures permettent au programmeur de substituer une paire de chevrons vides ( <>, appelée « opérateur diamant ») à une paire de chevrons contenant le ou les paramètres de type qu'un contexte suffisamment proche implique . Bien que <>soit souvent appelé « opérateur diamant », il ne s'agit pas d'un opérateur, mais simplement d'une liste de paramètres de type vide.

    Ainsi, l'exemple de code ci-dessus Entrypeut être réécrit comme suit :

    , "A" ); Entry < String , Integer > mark = new Entry <> ( "Mike" , 100 );System.out.printf ( " note : % s %n" , note); System.out.printf ( " note : % s% n " , note ) ;Entry < Integer , Boolean > prime = new Entry < > ( 13 , true ); if ( prime.getValue ( ) ) { System.out.println ( " % s est premier.%n" , prime.getKey ( ) ); } else { System.out.println ( " % s n'est pas premier.%n" , prime.getKey ( ) ) ; }

    L'opérateur dit « diamant » n'existe pas dans d'autres langages dérivés de Java comme C# ou Kotlin , qui utilisent leurs propres mécanismes pour éviter les informations de type redondantes. Bien qu'un opérateur visuellement similaire existe en C++ , il ne désigne pas la déduction du type du paramètre, mais fait simplement référence au type par défaut.

    borne<?> supérieure ou inférieure . Étant donné que le type exact représenté par un caractère générique est inconnu, des restrictions s'appliquent au type des méthodes pouvant être appelées sur un objet utilisant des types paramétrés.

    Voici un exemple où le type d'élément de a java.util.Collection<E>est paramétré par un caractère générique :

    `null` , qui est un membre de tout type.

    Pour spécifier la limite supérieure d'un type générique, le mot-clé est utilisé pour indiquer que l'argument de type est un sous-type de la classe englobante. Ainsi , cela signifie que la liste donnée contient des objets d'un type inconnu qui étend la classe. Par exemple, la liste pourrait être `List<T>` ou ` List<T>`. La lecture d'un élément de la liste renverra `List<T>` . L'ajout d'éléments nuls est également autorisé. null` (qui appartient à tout type).

    Le mnémonique PECS (Producer Extends, Consumer Super) du livre Effective Java de Joshua Bloch offre un moyen facile de se souvenir quand utiliser des caractères génériques (correspondant à la covariance et à la contravariance ) en Java.

    Clause générique relative aux jets

    Bien que les exceptions elles-mêmes ne puissent pas être génériques, les paramètres de type peuvent apparaître dans une clause throws :

    effacement de type . Par exemple, ` T` sera converti en `T` , un type non générique qui contient généralement des objets arbitraires. La vérification à la compilation garantit que le code résultant utilise le type correct. List<Integer>List

    En raison de l'effacement de type, les paramètres de type ne peuvent pas être déterminés à l'exécution. Par exemple, lorsqu'un `An` ArrayListest examiné à l'exécution, il n'existe aucun moyen général de déterminer si, avant l'effacement de type, il s'agissait d'un `An` ArrayList<Integer>ou d'un `An` ArrayList<Float>. Cette restriction est source d'insatisfaction pour beaucoup. Des solutions partielles existent. Par exemple, il est possible d'examiner chaque élément individuellement pour déterminer son type ; par exemple, si un `An` java.util.ArrayList<E>contient un `An` java.lang.Integer, ce dernier java.util.ArrayList<E>peut avoir été paramétré avec ` IntegerAn` (cependant, il peut avoir été paramétré avec n'importe quel parent de `An` java.lang.Integer, tel qu'un ` java.lang.NumberAn` ou un java.lang.Object`An`).

    Pour illustrer ce point, le code suivant affiche « Égal » :

    Égal " ) ; }

    Un autre effet de l'effacement de type est qu'une classe générique ne peut étendre la java.lang.Throwableclasse d'aucune manière, directement ou indirectement :

    Entier " ) ; } catch ( GenericException <String> e ) { System.err.println ( " Chaîne " ) ; }

    En raison de l'effacement de type, l'environnement d'exécution ne saura pas quel bloc catch exécuter, c'est pourquoi cela est interdit par le compilateur.

    Les génériques Java diffèrent des modèles C++ . Les génériques Java ne génèrent qu'une seule version compilée d'une classe ou d'une fonction générique, quel que soit le nombre de types paramétrés. De plus, l'environnement d'exécution Java n'a pas besoin de connaître le type paramétré, car cette information est validée à la compilation et n'est pas incluse dans le code compilé. Par conséquent, l'instanciation d'une classe Java de type paramétré est impossible, car elle nécessite un appel au constructeur, qui est indisponible si le type est inconnu.

    Par exemple, le code suivant ne peut pas être compilé :

    les variables statiques sont partagées entre toutes les instances de la classe, quel que soit leur paramètre de type. Par conséquent, ce paramètre ne peut être utilisé ni dans la déclaration des variables statiques, ni dans celle des méthodes statiques.

    L'effacement de type a été implémenté en Java pour maintenir la compatibilité ascendante avec les programmes écrits avant Java SE5.

    Différences avec les tableaux

    Il existe plusieurs différences importantes entre les tableaux (qu'il s'agisse de tableaux primitifs ou java.lang.Objectde tableaux génériques) et les génériques en Java. Deux des principales différences concernent la variance et la réification .

    Covariance, contravariance et invariance

    covariants . C’est un avantage de l’utilisation des génériques par rapport aux objets non génériques tels que les tableaux. Plus précisément, les génériques peuvent contribuer à prévenir les exceptions d’exécution en levant une exception à la compilation afin d’obliger le développeur à corriger le code.

    Par exemple, si un développeur déclare un java.lang.Object[]objet et l'instancie java.lang.Long[], aucune exception n'est levée à la compilation (les tableaux étant covariants). Cela peut donner l'impression erronée que le code est correct. Cependant, si le développeur tente d'ajouter un élément java.lang.Stringà cet java.lang.Long[]objet, le programme lèvera une exception java.lang.ArrayStoreException. Cette exception d'exécution peut être complètement évitée si le développeur utilise les génériques.

    Si le développeur déclare un compilateur Java lèvera (à juste titre) une exception à la compilation pour signaler la présence de types incompatibles (puisque les génériques sont invariants). Ceci permet d'éviter d'éventuelles exceptions à l'exécution. Ce problème peut être résolu en créant une instance de `T` à l' opérateur diamant ` in`.

    Réification

    réifiés , ce qui signifie qu'un objet tableau impose ses informations de type à l'exécution, contrairement aux génériques en Java. En effet, les tableaux sont de véritables classes générées par le compilateur dans la JVM ; à chaque type T, il existe un type T[].

    Plus formellement, les objets de type générique en Java sont des types non réifiables. Un type non réifiable est un type dont la représentation à l'exécution contient moins d'informations que sa représentation à la compilation.

    En Java, les objets de type générique ne sont pas réifiables en raison de l'effacement de type. Java n'impose les informations de type qu'à la compilation. Une fois vérifiées, ces informations sont supprimées et ne sont donc plus disponibles à l'exécution.

    Des exemples de types non réifiables incluent Le projet Valhalla est un projet expérimental visant à développer des génériques Java et des fonctionnalités du langage améliorés, pour les versions futures potentiellement à partir de Java 10. Les améliorations potentielles incluent :

    Plus d articles de Worldlex Wiki

    Revenez a l index pour explorer davantage de pages sur l histoire, la science, la culture, la geographie et la societe en francais.

    Explorer l index