La généricité
Présentation
La généricité consiste à créer des algorithmes qui peuvent s’appliquer à différents types de données. À la place de définir des algorithmes spécifiques pour chaque type de données, un unique algorithme est développé de manière générique. Il est ensuite possible de l’utiliser avec un type de données bien précis.
Les procédures et les fonctions génériques
1. La déclaration
Les procédures et les fonctions peuvent être génériques. Elles sont alors paramétrées par un ou plusieurs types.
Prenons l’exemple d’une procédure affichant l’ensemble des éléments d’un tableau. Quel que soit le type des éléments contenus dans le tableau, les opérations à réaliser sont les mêmes : parcourir les différentes cases du tableau et pour chacune afficher la valeur contenue. Il serait dommage de devoir en écrire une pour afficher un tableau d’entiers, une autre pour un tableau de texte, une autre pour un tableau de dates...
Une seule procédure générique est alors écrite, paramétrée par un type (le type d’un élément du tableau). Lorsque nous avons besoin d’utiliser cette procédure pour afficher un tableau avec un type bien précis d’éléments, des entiers par exemple, le compilateur va automatiquement remplacer le type générique par le type entier.
Syntaxe pour déclarer une procédure générique :
Procédure nomDeLaProcedure<TypeGénérique>(listeDesParamètres)
Exemple :
Procédure afficherTableau<T>(tab : T[], taille : entier)
Variable i : entier
Début
écrireSRC("[" & tab[0])
Pour i <- 1 à taille - 1
écrireSRC(", " & tab[i])
FPour
écrire("]")*
Fin
Le type générique est indiqué entre un couple de chevrons. Dans cet exemple, ce type a été nommé T (comme type). Il est assez fréquent de le nommer ainsi, mais vous êtes libre de le nommer comme vous le souhaitez.
Syntaxe pour déclarer une fonction générique :
Fonction nomDeLaProcedure<TypeGénérique>(listeDesParamètres)
...
Les classes génériques
1. La déclaration
Les classes peuvent également être paramétrées par un ou plusieurs types. Elles auront dans ce cas un ou plusieurs attributs de type générique.
Une classe générique ne possède pas nécessairement de méthodes génériques et une méthode générique n’est pas nécessairement déclarée au sein d’une classe générique.
Syntaxe pour déclarer une classe générique :
Classe NomClasse<TypeGénérique>
Attribut nomAttribut : TypeGénérique
...
FClasse
Exemple :
Classe Couple<U, V>
Attribut element1 : U
Attribut element2 : V
Méthodes
Constructeur(un : U, deux : V)
Début
instance.element1 <- un
instance.element2 <- deux
Fin
Fonction getElement1() Retourne U
Début
Retourner instance.element1
Fin
Fonction getElement2() Retourne V
Début
Retourner instance.element2
Fin ...
Les interfaces génériques
Les interfaces permettent de regrouper un ensemble de méthodes abstraites et éventuellement de constantes (cf. chapitre Les éléments abstraits). Les interfaces peuvent être paramétrées par un ou plusieurs types.
Une interface générique peut également imposer des contraintes sur son ou ses types génériques.
1. La déclaration
Syntaxe :
Interface NomDeLInterface<TypeGénérique>
...
FInterface
Cette interface générique contiendra une ou plusieurs méthodes abstraites utilisant ce type générique.
Exemple :
Interface EnsembleTrie<T ordonné>
Procédure abstraite ajouter(valeur : T)
Procédure abstraite retirer(position : entier)
Fonction abstraite elementEn(position : entier) Retourne T
FInterface
Cette interface permet de gérer un ensemble de données. La particularité c’est que les éléments présents dans cet ensemble sont triés. Cette interface est paramétrée par le type T correspondant aux types des éléments contenus dans cet ensemble. La contrainte ordonnée a été positionnée sur ce type pour que les éléments puissent être comparés entre eux et positionnés au bon emplacement.
La première méthode abstraite prend en paramètre un élément de type T. Si un ensemble trié d’entiers est créé, ce type T sera remplacé par entier. La seconde méthode abstraite n’utilise pas le type T. Quel que soit le type générique utilisé cette méthode ne change pas. Enfin, la dernière méthode abstraite retourne une valeur de type T. Son objectif est de retourner la valeur à la position passée en paramètre. Elle retourne donc bien une valeur de type T.
2. L’implémentation
Une classe peut implémenter une ou plusieurs interfaces, qu’elles soient ou ne soient pas génériques. Lorsqu’une classe implémente une interface générique, elle a deux possibilités : soit elle indique par quoi le type générique...
La généricité en Java
Java a introduit la généricité depuis la version 5 du JDK. Il est possible de créer des méthodes génériques, des interfaces génériques ainsi que des classes génériques. De plus, dans la bibliothèque standard, des méthodes, interfaces et classes génériques sont disponibles et peuvent directement être utilisées.
La généricité est traitée au moment de la compilation. Lors de cette étape, les types utilisés sont vérifiés. Mais le code compilé est non générique car la machine virtuelle Java est restée non générique même après la version 5 de Java.
1. Les méthodes génériques
a. La déclaration
Pour définir une méthode générique, il faut ajouter le type générique entre un couple de chevrons au sein de la déclaration de la méthode juste avant le type de retour.
Syntaxe :
visibilité [static] <TypeGénérique> typeRetour
nomDeLaMéthode(listeDesParamètres) {
...
}
Exemple :
public static <T> void afficherTableau(T[] tab) {
System.out.print("[");
if (tab.length > 0) {
System.out.print(tab[0]);
for (int i = 1; i < tab.length; i++) {
System.out.print(", " + tab[i]);
}
}
System.out.println("]");
}
b. L’appel
De manière similaire à ce qui se fait en algorithmique, il est possible d’appeler la méthode normalement et en fonction des paramètres, le type concret devant remplacer le type générique est déduit automatiquement.
Exemple :
String[] tabTexte = new String[3];
tabTexte[0] = "Algo";
tabTexte[1] = "Généricité";
tabTexte[2] = "Java";
afficherTableau(tabTexte);
L’appel à la méthode générique...
Exercices
1. Tirage au sort
Écrire une fonction générique prenant en paramètre un tableau contenant des éléments de type générique et sa taille et retourne un des éléments du tableau choisi aléatoirement.
Réaliser un algorithme utilisant cette fonction générique pour réaliser un tirage au sort parmi un ensemble de participants.
2. À l’envers
Définir une procédure générique inversant l’ordre des éléments d’un tableau.
Faire appel à cette procédure dans un algorithme.
3. Se mélanger les pinceaux
Créer une procédure générique mélangeant les éléments d’un tableau.
Un pinceau est caractérisé par sa forme et sa taille. Créer une classe Pinceau.
Écrire un algorithme créant un tableau de pinceaux, mélangeant l’ordre des pinceaux et affichant l’ensemble des pinceaux.
4. Le plus grand
Créer une fonction générique retournant la plus grande valeur parmi trois passées en paramètre.
Réaliser un algorithme utilisant cette fonction.
5. Procédures et fonctions génériques en Java
Implémenter les exercices précédents en Java
6. Apprentissage liste de vocabulaire
Réaliser un programme en Java permettant l’apprentissage...
Correction des exercices
1. Tirage au sort
Fonction tirageAuSort<T>(tableau : T[], taille : entier)
Retourne T
Début
Retourner tableau[aléa(0, taille - 1)]
Fin
Algo GagnantTireAuSort
Constante TAILLE : entier <- 4
Variable participants : texte[TAILLE]
Variable gagnant : texte
Début
participants[0] <- "Tom"
participants[1] <- "Aude"
participants[2] <- "Jade"
participants[3] <- "Félix"
gagnant <- tirageAuSort(participants, TAILLE)
écrire("Le gagnant est " & gagnant)
Fin
2. À l’envers
Procédure reverse<T>(tableau : T[], taille : entier)
Variable tmp : T
Variable i : entier
Début
Pour i <- 0 à taille div 2 - 1
tmp <- tableau[i]
tableau[i] <- tableau[taille - 1 - i]
tableau[taille - 1 - i] <- tmp
FPour
Fin
Algo TestReverse
Constante TAILLE : entier <- 10
Variable valeurs : entier[TAILLE]
Variable i : entier
Début
Pour i <- 0 à TAILLE - 1
valeurs[i] <- i
FPour
reverse(valeurs, TAILLE)
Pour i <- 0 à TAILLE - 1
écrire(valeurs[i])
FPour
Fin
3. Se mélanger les pinceaux
Procédure melange<T>(tableau : T[], taille : entier)
Variable tmp : T
Variable i, posAlea1, posAlea2 : entier
Début
Pour i <- 0 à taille - 1
posAlea1 <- aléa(0, taille - 1)
posAlea2 <- aléa(0, taille - 1)
Si posAlea1 ≠ posAlea2 Alors
...