La Généricité
Cette leçon aborde le concept de généricité en programmation orientée objet, plus particulièrement dans le langage Java. Elle s’inscrit dans un cours avancé de programmation orientée objet et explique comment la généricité permet d’écrire du code réutilisable et robuste en évitant la duplication et les erreurs liées au typage.
D'après le document La Généricité
Cet article a été rédigé automatiquement à partir du document source, puis vérifié avant publication.

Document source
Object-Oriented Programming (Advanced) · Institut Supérieur des Études Technologiques · PDF · 19 pages
Afficher l'aperçu du document
Cette leçon aborde le concept de généricité en programmation orientée objet, plus particulièrement dans le langage Java. Elle s’inscrit dans un cours avancé de programmation orientée objet et explique comment la généricité permet d’écrire du code réutilisable et robuste en évitant la duplication et les erreurs liées au typage.
Motivation de la généricité
Imaginons que l’on souhaite développer un code pour gérer une file d’attente (FIFO - First In First Out) pouvant contenir différents types de données : entiers, chaînes de caractères, objets Personne, etc. Une première solution consiste à écrire une classe spécifique pour chaque type de valeur que l’on veut stocker dans la file.
Par exemple, une classe ElementInt pour gérer des entiers :
class ElementInt {
private final int value;
private ElementInt next;
ElementInt(int val) {
this.value = val; next = null;
}
void inserer(ElementInt elt) {
this.next = elt;
}
int getValue() { return value; }
ElementInt getNext() { return next; }
}
Et une classe FileAttenteInt qui utilise cette classe élément :
public class FileAttenteInt {
private ElementInt tête = null;
private ElementInt queue = null;
public void ajouter(int val) {
ElementInt elt = new ElementInt(val);
if (estVide()) { tête = queue = elt; }
else { queue.inserer(elt); queue = elt; }
}
public boolean estVide() { return tête == null; }
public int retirer() {
int val = -1;
if (!estVide()) {
val = tête.getValue();
tête = tête.getNext();
}
return val;
}
}
On peut faire de même pour une file d’objets Personne, mais cela conduit à une duplication importante du code, source d’erreurs et difficile à maintenir. Il faut aussi prévoir toutes les combinaisons possibles, ce qui est peu pratique.
Utilisation d’un type universel : Object
Une deuxième solution consiste à utiliser un type universel, comme Object en Java, qui est la superclasse de toutes les classes. Ainsi, on peut écrire une seule classe Element et une seule classe FileAttente capables de gérer n’importe quel type d’objet :
class Element {
private final Object value;
private Element next;
Element(Object val) {
this.value = val; next = null;
}
void inserer(Element elt) {
this.next = elt;
}
public Object getValue() {
return value;
}
public Element getNext() {
return next;
}
}
public class FileAttente {
private Element tête = null;
private Element queue = null;
public void ajouter(Object val) {
Element elt = new Element(val);
if (estVide()) { tête = queue = elt; }
else { queue.inserer(elt); queue = elt; }
}
public boolean estVide() { return tête == null; }
public Object retirer() {
Object val = null;
if (!estVide()) {
val = tête.getValue();
tête = tête.getNext();
}
return val;
}
}Cette approche permet d’utiliser la même file d’attente pour stocker des objets de types différents, par exemple :
FileAttente f1 = new FileAttente();
f1.ajouter(new Personne(...));
FileAttente f2 = new FileAttente();
f2.ajouter(new Voiture(...));
Limites de l’utilisation de Object et transtypage
Le principal problème de cette solution est qu’il faut effectuer un transtypage (cast) explicite pour récupérer les objets dans leur type d’origine. Par exemple :
String nom = ((Personne) (f1.retirer())).getNom();
Si un élément de type incorrect est inséré, par exemple une chaîne de caractères dans une file censée contenir des Personnes, une ClassCastException sera levée à l’exécution :
f1.ajouter("Hello");
String nom = ((Personne) (f1.retirer())).getNom(); // ClassCastException
Cette obligation de transtypage rend le code plus lourd, moins lisible et plus difficile à maintenir, avec un risque accru d’erreurs d’exécution.
Introduction de la généricité en Java
Depuis Java 1.5 (Tiger), la généricité a été introduite pour résoudre ces problèmes. La généricité, ou polymorphisme paramétrique, permet de faire des abstractions sur les types en paramétrant une classe, une interface ou une méthode par un ou plusieurs types. Cela permet de réutiliser le même code avec différents types de données tout en conservant la sécurité de type au moment de la compilation.
La généricité existait déjà dans de nombreux langages comme Eiffel, Ada, C++ ou Haskell, et est également présente dans le langage de modélisation UML.
Principe de la généricité
Les classes Element et FileAttente deviennent paramétrées par un type formel T. Ce type T est utilisé comme type des attributs et méthodes, mais la classe ignore la nature exacte de ce type :
public class FileAttente<T> {
private Element<T> tête = null;
...
}
Lors de l’instanciation, on remplace le paramètre formel T par un type concret :
FileAttente<Personne> f1 = new FileAttente<Personne>();
f1.ajouter(new Personne(...));
FileAttente<Voiture> f2 = new FileAttente<>();
f2.ajouter(new Voiture(...));
Depuis Java 7, grâce à la syntaxe dite "Diamond", il n’est plus obligatoire de répéter les arguments de type dans le constructeur.
Avantages de la généricité
La généricité simplifie la programmation en évitant la duplication du code et en rendant le code plus robuste. Elle permet d’écrire des classes et interfaces paramétrées par des types, ce qui assure la sécurité de type à la compilation et évite les transtypages risqués à l’exécution.
Types génériques simples
Un type générique est une classe ou interface paramétrée par un ou plusieurs types. Par exemple, une classe générique à deux paramètres :
public class Paire<T1, T2> {
private final T1 first;
private final T2 second;
public Paire(T1 first, T2 second) {
this.first = first;
this.second = second;
}
public T1 getFirst() { return first; }
public T2 getSecond() { return second; }
}Une interface générique classique est Comparable<T> :
public interface Comparable<T> {
int compareTo(T o);
}
Instanciation d’un type générique
Instancier un type générique consiste à fournir des arguments de type (paramètres effectifs) qui peuvent être :
- Des classes concrètes ou abstraites, par exemple :
Paire<String, Forme> c1 = new Paire<>("Cercle 1", new Cercle(0,0,10)); - Des interfaces, par exemple :
Paire<String, IDessinable> c2 = new Paire<>("Visage 1", new VisageRond()); - Des types paramétrés, par exemple :
Paire<String, Paire<String, Forme>> c3 = new Paire<>("Paire 3", c1); - Des paramètres de type dans une classe générique.
Compilation et effacement des types
La compilation du code générique repose sur un mécanisme appelé effacement (erasure). À l’exécution, il n’existe qu’une seule classe, appelée type brut, partagée par toutes les instanciations du type générique.
Cela entraîne certaines limitations :
- Les membres statiques d’une classe paramétrée sont partagés par toutes les instanciations, quelle que soit la substitution des paramètres de type.
- Les membres statiques ne peuvent pas utiliser les paramètres de type.
- Au sein d’une classe paramétrée, on ne peut pas utiliser les paramètres de type pour instancier directement un objet ou un tableau.
- Seul le type brut est connu à l’exécution, ce qui limite certaines opérations liées au type.
Points clés
- La généricité permet d’écrire du code réutilisable et sûr en paramétrant les classes, interfaces et méthodes par des types.
- Avant la généricité, on dupliquait le code pour chaque type ou on utilisait
Object, ce qui nécessitait des transtypages risqués. - La généricité évite les erreurs de transtypage et améliore la lisibilité et la maintenance du code.
- La compilation générique utilise l’effacement, ce qui entraîne des limitations sur l’utilisation des paramètres de type dans les membres statiques et les instanciations.
- Les types génériques peuvent avoir plusieurs paramètres et être instanciés avec des classes, interfaces ou autres types génériques.
Commentaires
Aucun commentaire pour le moment. Posez la première question.