You are on page 1of 15

Aller au menu - Aller au contenu

• Informatique
• Bientôt...

• 1 190 Zéros connectés -
• 153 910 Zéros inscrits

Inscription
Inscription rapide en 2 minutes

Connexion
Connexion pour les membres inscrits

+


• Cours
• Forums
• Participez
• Études

Cours

• Site Web
XHTML / CSS
PHP / MySQL
Java EE (J2EE)

• Programmation
Langage C
Langage C++
Langage Java
Langage Java (API)

• Système alternatif
Linux

• Modélisation 3D
Blender
Maya Nouveau !
• Tutoriel du jour
Un serveur d'hébergement multiutilisateur sous Linux
Plus

> Le Site du Zéro > Cours > Tutoriels > Programmation > Java > Programmation en Java > Java Orienté Objet >
La généricité en Java > Lecture du tutoriel

La généricité en Java

• Tutoriel
• 21 commentaires
• Historique

Informations sur le tutoriel

Auteur : cysboy
Difficulté :
Visualisations : 61 805

Licence :

Plus d'informations

Historique des mises à jour

• Le 30/10/2009 à 12:14:00
Publication sous licence CC-BY-NC-SA
• Le 13/10/2009 à 21:35:29
Correction nom de classe.
• Le 03/10/2009 à 12:05:11
Correction d'un bug de QCM

Pour ce concept, ajouté au JDK depuis sa version 1.5, nous allons surtout travailler avec des exemples tout au
long de ce chapitre.
Le principe de la généricité est de faire des classes qui n'acceptent qu'un certain type d'objet ou de donnée, mais
de façon dynamique ! :D.

Avec ce que nous avons vu au chapitre précédent, vous avez sûrement poussé un soupir de soulagement lorsque
vous avez vu que ces objets acceptent tous types de données !
Par contre, un problème de taille se pose : lorsque vous voudrez travailler avec ces données, vous allez devoir
faire un cast ! Et peut-être même un cast de cast, voire même un cast de cast de cast...
C'est là que se situe le problème... Mais comme je vous le disais, depuis la version 1.5 du JDK, la généricité est
là pour vous aider !
Sommaire du chapitre :

• Notion de base
• Plus loin dans la généricité !
• Généricité et collection
• Héritage et généricité
• Ce qu'il faut retenir
• Q.C.M.

Aller

Notion de base

Bon, pour vous montrer la puissance de la généricité, nous allons tout de suite voir un exemple de classe qui ne
l'utilise pas !
Ne vous en faites pas... Ayez confiance en moi...

Il y a un exemple très simple, que vous pourrez retrouver aisément sur le net, car il s'agit d'un des cas les plus
simples pour expliquer les bases de la généricité. Nous allons coder une classe Solo. Celle-ci va travailler avec
des références de type String.
Voici le diagramme de classe :

Vous pouvez voir que le code de cette classe est très rudimentaire ! On affecte une valeur, on peut la mettre à
jour et la récupérer...
Maintenant, si je vous demande de me faire une classe qui permette de travailler avec n'importe quel type de
données, j'ai une vague idée de ce que vous allez faire... Ce ne serait pas un truc comme ça :

J'en étais sûr... :D. Créez-la et créez-vous aussi une classe avec une méthode main !
Mais si vous voulez utiliser les données de l'objet Solo, vous allez devoir faire un cast.
Testez ce code dans votre main :

Code : Java
1 public class Test {
2
3 public static void main(String[] args) {
4
5 Solo val = new Solo(12);
6 int nbre = val.getValeur();
7 }
8
9}

Vous constatez que vous tentez vainement de mettre un objet de type Object dans un objet de type Integer. Ceci
est interdit ! !
La classe Object est plus globale que la classe Integer, vous ne pouvez donc pas faire cette manipulation, sauf si
vous "castez" votre objet en Integer, comme ceci :

Code : Java
1 public class Test {
2
3 public static void main(String[] args) {
4
5 Solo val = new Solo(12);
6 int nbre = (Integer)val.getValeur();
7 }
8
9}

Pour le moment, on peut dire que votre classe peut travailler avec tous les types de données, mais les choses se
corsent un peu à l'utilisation... Vous serez peut-être tentés de faire une classe par type de donnée (SoloInt,
SoloString).
Et c'est là que la généricité est pratique. Car avec ceci, vous allez pouvoir savoir ce que contient votre objet Solo,
et vous n'aurez qu'une seule classe à développer !
Voilà le diagramme de classe de cet objet :

Et voici son code :

Code : Java
1 public class Solo<T> {
2
3 /**
4 * Variable d'instance
5 */
6 private T valeur;
7
/**
8
* costructeur par défaut
9
*/
10
public Solo(){
11
this.valeur = null;
12
}
13
14
/**
15
* Constructeur avec paramètre
16
* Inconnu pour l'instant
17
* @param val
18
*/
19
public Solo(T val){
20
this.valeur = val;
21
}
22
23
24
/**
25
* Définit la valeur avec le paramètre
26
* @param val
27
*/
28
public void setValeur(T val){
29
this.valeur = val;
30
}
31
32
/**
33
* retourne la valeur déjà "castée" par la signature de la
34
méthode !
35
* @return
36
*/
37
public T getValeur(){
38
return this.valeur;
39
}
40
}

Impressionnant, n'est-ce pas ?
Dans cette classe, le T n'est pas encore défini. Vous le ferez à l'instanciation de cette classe. Par contre, une fois
instancié avec un type, l'objet ne pourra travailler qu'avec le type de données que vous lui avez spécifié !
Exemple de code :

Code : Java
1 public class Test {
2
3 public static void main(String[] args) {
4 // TODO Auto-generated method stub
5 Solo<Integer> val = new Solo<Integer>(12);
6 int nbre = val.getValeur();
7 }
8}

Ce code fonctionne très bien, mais si vous essayez de faire ceci :
Code : Java
public class Test {
1
2
public static void main(String[] args) {
3
// TODO Auto-generated method stub
4
Solo<Integer> val = new Solo<Integer>("toto"); //Ici on
5
essaie de mettre une chaîne de caractère à la place d'un entier
6
int nbre = val.getValeur();
7
}
8
}
...ou encore ceci :
Code : Java
public class Test {
1
2
public static void main(String[] args) {
3
// TODO Auto-generated method stub
4
Solo<Integer> val = new Solo<Integer>(12);
5
val.setValeur(12.2f); //Ici on essaie de mettre un float
6
à la place d'un entier
7
}
8
}

...vous verrez une erreur dans votre zone de saisie. Ceci vous indique que votre objet ne reçoit pas le bon type
d'argument, ou que votre réceptacle n'a pas le bon type de données ! Dans tous les cas de figure, il y a conflit
entre le type de données que vous avez passé à votre instance lors de sa création et le type de données que vous
essayez d'utiliser avec celle-ci !
Par contre, vous devez savoir que cette classe ne fonctionne pas seulement avec des Integer. Vous pouvez
utiliser tous les types que vous souhaitez !

Voici une démonstration de mes dires :
Code : Java
1 public class Test {
2
3 public static void main(String[] args) {
4 // TODO Auto-generated method stub
5 Solo<Integer> val = new Solo<Integer>();
6 Solo<String> valS = new Solo<String>("TOTOTOTO");
7 Solo<Float> valF = new Solo<Float>(12.2f);
8 Solo<Double> valD = new Solo<Double>(12.202568);
9 }
10 }

Vous devez avoir remarqué que je n'ai pas utilisé ici les types de données que vous utilisez pour déclarer des
variables de type primitif ! Ce sont les classes de ces types primitifs !

En effet, lorsque vous déclarez une variable de type primitif, vous pouvez utiliser leurs classes de définition,
mais c'est rarement utilisé car très lourd à la lecture. Par exemple :
Code : Java
public class Test{
1
2
public static void main(String[] args){
3
4
int i = new Integer(12); // est équivalent à int i = 12;
5
double d = new Double(12.2586); // est équivalent à double d =
6
12.2586;
7
//...
8
}
9
}

Bon ! Maintenant que vous avez un bel exemple de généricité, nous allons complexifier un peu les choses !

Plus loin dans la généricité !
Vous devez savoir que la généricité peut être multiple !
Nous avons créé une classe Solo, mais rien ne vous empêche de créer une classe Duo, qui elle, prend deux
paramètres génériques ! Voici la modélisation de cette classe :

Vous pouvez voir que cette classe prend deux types de références non encore définies !
Voilà le code source de cette classe :
Code : Java
1 public class Duo<T, S> {
2
3 /**
4 * Variable d'instance de type T
5 */
6 private T valeur1;
7 /**
8 * Variable d'instance de type S
9 */
10 private S valeur2;
11
12 /**
13 * Constructeur par défaut
14 */
15 public Duo(){
16 this.valeur1 = null;
17 this.valeur2 = null;
18 }
19
20 /**
21 * Constructeur avec paramètres
22 * @param val1
23 * @param val2
24 */
25 public Duo(T val1, S val2){
26 this.valeur1 = val1;
27 this.valeur2 = val2;
28 }
29
30 /**
31 * Méthodes d'initialisation des deux valeurs
32 * @param val1
33 * @param val2
34 */
35 public void setValeur(T val1, S val2){
36 this.valeur1 = val1;
37 this.valeur2 = val2;
38 }
39
40 /**
41 * Retourne la valeur T
42 * @return
43 */
44 public T getValeur1() {
45 return valeur1;
46 }
47
48 /**
49 * Définit la valeur T
50 * @param valeur1
51 */
52 public void setValeur1(T valeur1) {
53 this.valeur1 = valeur1;
54 }
55
56 /**
57 * retourne la valeur S
58 * @return
59 */
60 public S getValeur2() {
61 return valeur2;
62 }
63
64 /**
65 * définit la valeur S
66 * @param valeur2
67 */
68 public void setValeur2(S valeur2) {
69 this.valeur2 = valeur2;
70 }
71
72 }

Voici un code que vous pouvez tester :
Code : Java
public class Test {

1 public static void main(String[] args) {
2
3 Duo<String, Boolean> dual = new Duo<String,
4 Boolean>("toto", true);
5 System.out.println("Valeur de l'objet dual: val1 = " +
6 dual.getValeur1() + ", val2 = " + dual.getValeur2());
7
8 Duo<Double, Character> dual2 = new Duo<Double,
9 Character>(12.25895, 'C');
10 System.out.println("Valeur de l'objet dual2: val1 = " +
11 dual2.getValeur1() + ", val2 = " + dual2.getValeur2());
12
}
}

Et voici le résultat :
Vous voyez qu'il n'y a rien de bien méchant ici. Ce principe fonctionne exactement comme l'exemple précédent.
La seule différence est dans le fait qu'il n'y a pas un, mais deux paramètres génériques !
Attends une minute... Lorsque je déclare une référence de type Duo<String, Boolean>, je ne peux plus la
changer en un autre type !

En fait, avec ce que je vous ai fait voir, non.
Pour le moment, si vous faites :
Code : Java
public class Test {
1
2 public static void main(String[] args) {
3
4 Duo<String, Boolean> dual = new Duo<String,
5 Boolean>("toto", true);
6 System.out.println("Valeur de l'objet dual: val1 = " +
7 dual.getValeur1() + ", val2 = " + dual.getValeur2());
8 dual = new Duo<Double, Character>();
9 }
}

vous violez la contrainte que vous avez émise lors de la déclaration du type de référence ! Mais il existe un
moyen de contourner ça.
Tout simplement en disant, à la déclaration, que votre objet va accepter tout types de références ! Comment en
utilisant ce qu'on appelle le wildcard : ?.
Comme ceci :
Code : Java
public class Test {
1
2
public static void main(String[] args) {
3
4
Duo<?, ?> dual = new Duo<String, Boolean>("toto", true);
5
6
System.out.println("Valeur de l'objet dual: val1 = " +
7
dual.getValeur1() + ", val2 = " + dual.getValeur2());
8
dual = new Duo<Double, Character>();
9
dual = new Duo<Integer, Float>();
10
dual = new Duo<Solo, Solo>();
11
}
12
}

Avec ce type de déclaration, votre objet accepte bien n'importe quel type de référence !
Intéressant, non ?
Donc si vous suivez bien, on va pouvoir encore corser la chose !

Généricité et collection

Vous pouvez aussi utiliser la généricité sur les objets servant à gérer des collections.
C'est même l'un des points les plus utiles de la généricité !
En effet, lorsque vous listiez le contenu d'un ArrayList par exemple, vous n'étiez JAMAIS sûrs à 100 % de
savoir sur quel type de référence vous alliez tomber... Eh bien ce calvaire est terminé et le polymorphisme va
pouvoir réapparaître, plus puissant que jamais !

Voyez comment utiliser (même si vous l'aviez deviné) la généricité avec les collections :
Code : Java
1 import java.util.ArrayList;
2
3
4 public class Test {
5
6 public static void main(String[] args) {
7
8 System.out.println("Liste de String");
9 System.out.println("------------------------------");
10 ArrayList<String> listeString= new ArrayList<String>();
11 listeString.add("Une chaîne");
12 listeString.add("Une Autre");
13 listeString.add("Encore une autre");
14 listeString.add("Allez, une dernière");
15
16 for(String str : listeString)
17 System.out.println(str);
18
19
20
21 System.out.println("\nListe de float");
22 System.out.println("------------------------------");
23
24 ArrayList<Float> listeFloat = new ArrayList<Float>();
25 listeFloat.add(12.25f);
26 listeFloat.add(15.25f);
27 listeFloat.add(2.25f);
28 listeFloat.add(128764.25f);
29
30 for(float f : listeFloat)
31 System.out.println(f);
32 }
33 }

Voici le résultat de ce code :
La généricité sur les listes est régi par les même lois vues précédemment !
Pas de type float dans un ArrayList<String>.

Vu qu'on y a crescendo, on pimente à nouveau le tout !

Héritage et généricité

Là où les choses sont pernicieuses, c'est quand vous utilisez des classes usant de la généricité avec des objets
usant de la notion d'héritage !
L'héritage dans la généricité est une des choses les plus complexes à comprendre en Java. Pourquoi ? Tout
simplement parce qu'elle va à l'encontre de ce que vous avez appris jusqu'à présent...

Acceptons le postulat suivant

Nous avons une classe Voiture dont hérite une autre classe VoitureSansPermis, ce qui nous donnerait le
diagramme suivant :

Jusque-là, c'est simplissime.
Maintenant, ça se complique :
Code : Java
import java.util.ArrayList;
1
2
3 public class Test {
4
5 public static void main(String[] args) {
6
7 ArrayList<Voiture> listVoiture = new
8 ArrayList<Voiture>();
9 ArrayList<VoitureSansPermis> listVoitureSP = new
10 ArrayList<VoitureSansPermis>();
11
12 listVoiture = listVoitureSP;//Interdit ! ! ! !
13 }
}

Je sais que même si vous aviez l'habitude de la covariance des variables, ceci n'existe pas sous cette forme avec
la généricité !
Pourquoi cela ?

Imaginez deux secondes que l'instruction interdite soit permise !
Dans listVoiture, vous avez le contenu de la liste des voitures sans permis, et rien ne vous empêche d'ajouter
une voiture... Là où le problème prend toute son envergure, c'est lorsque vous allez vouloir sortir toutes les
voitures sans permis de votre variable listVoiture, eh oui ! Vous y avez rajouté une voiture !
Lors du balayage de la liste vous aurez, à un moment, une référence de type VoitureSansPermis à qui vous
tentez d'affecter une référence de type Voiture. Voilà pourquoi ceci est INTERDIT ! !

L'une des solutions consiste à utiliser le wildcard.
Je vais maintenant vous indiquer quelque chose d'important !
Avec la généricité, vous pouvez aller encore plus loin... Nous avons vu comment restreindre le contenu d'une de
nos listes. Mais nous pouvons aussi élargir son contenu ! Si je veux par exemple qu'un ArrayList puisse avoir
toutes les instances de Voiture et de ses classes filles. Comment faire ?

Ce qui suit s'applique aussi aux interfaces susceptibles d'être implémentées par une classe !

Attention les yeux, ça pique :
Code : Java
import java.util.ArrayList;
1 public class Test {
2
3 public static void main(String[] args) {
4 //Voici un ArrayList n'acceptant que des instances de
5 Voiture ou de ses sous-classes
6 ArrayList<? extends Voiture> listVoitureSP = new
7 ArrayList();
8 }
}

Et une application de ceci consiste à faire des méthodes génériques, comme par exemple avoir une méthode qui
permette de lister toutes les valeurs de notre ArrayList citée précédemment. Voici :
Code : Java
import java.util.ArrayList;
1
public class Test {
2
3
public static void main(String[] args) {
4
5
ArrayList<? extends Voiture> listVoitureSP = new
6
ArrayList();
7
afficher(listVoitureSP);
8
}
9
10
/**
11
* Méthode générique !
12
* @param <T>
13
* @param list
14
*/
15
static void afficher(ArrayList<? extends Voiture> list){
16
for(Voiture v : list)
17
System.out.println(v.toString());
18
}
19
20
}

Je conçois bien que ceci est un peu ardu à comprendre... Mais vous en aurez sûrement besoin dans une de vos
prochaines applications !
C'est vrai que ce code n'affiche rien... Mais rien ne vous empêche de développer un peu ces deux classes et de
faire des tests.

Bon : je crois que nous avons fait un bon tour du sujet même si nous n'avons pas tout abordé... Allez, le topo
classique.

Ce qu'il faut retenir
• La générécité est apparue depuis le JDK 1.5.
• Ce concept est très utile pour développer des objets pouvant travailler avec plusieurs types de données.
• Vous passerez donc moins de temps à développer des classes pour traiter de façon identique des
données différentes.
• La généricité permet de ré-utiliser le polymorphisme sans risque avec les collections.
• Cela vous permet une meilleure robustesse du code.
• Vous pouvez coupler les collections avec la généricité !
• Le wildcard (?) permet de dire que n'importe quel type peut être traité et donc accepté !
• Vous pouvez élargir le champ d'acceptation d'une collection générique grâce au mot clé extends.
• Pour ce genre de cas, les méthodes génériques sont particulièrement adpatées et permettent d'utiliser
le polymorphisme dans toute sa splendeur !

Q.C.M.

Qu'est-ce que la généricité ?

• Un concept permettant de ne pas spécifier de type précis pour une classe, une collection ou une
méthode afin d'avoir du code réutilisable.

• Ce qui lance le début de mes dessins animés préférés...

• Je ne sais pas.

• Pouvoir avoir une classe, une méthode ou une collection figée pour chaque utilisation.

Quel est le symbole du wildcard ?

• #

• @

• ?

• ~

À quoi sert le wildcard ?

• Je ne sais pas.

• À dire qu'une méthode, une classe ou une collection ne doit pas prendre le type passé en
paramètre.

• Il annonce que tous les types de données sont acceptés.

• Il annonce que tous les types de données sont rejetées.

Quel mot clé peut suivre le wildcard afin de dire que les types de données passées en paramètres peuvent être
des enfants d'une classe ?

• implements.

• extends.

• implement.

• extend.
Si j'ai une classe ObjectA qui implémente une interface Face, que dois-je mettre comme paramètre de généricité
à une ArrayList pour que les types acceptés soient du type de mon interface ou des objets qui l'implémentent ?

• ArrayList<? implements Face> list = new ArrayList();

• ArrayList<? generics Face> list = new ArrayList();

• ArrayList<? extends Face> list = new ArrayList();

Correction !

Statistiques de réponses au QCM

J'espère que ce chapitre n'a été trop... lourd...
En attendant, nous avons presque terminé cette seconde partie... La programmation d'interface graphique se
rapproche !
Mais il nous reste une dernière chose à aborder qui peut s'avérer importante ! La réflexivité.
Aller

Informations sur le tutoriel

Retour en haut

Créé : Le 21/06/2006 à 15:02:22
Modifié : Le 23/07/2009 à 18:50:41
Avancement : 100%

21 commentaires

• Le Site du Zéro
o Plan du site
o L'équipe
o Mentions légales
o Accessibilité
o Livre d'or
• Simple IT
o Qui sommes-nous ?
o Recrutement
o Notre blog
• Contacts
o Espace annonceurs
o Espace presse
o Nous contacter
o Signaler un bug
• Partenaires
o Hébergement web
o Correction tutoriels
o Créer un site
• Suivez-nous
o Flux RSS News, Cours, Blog
o Twitter
o Facebook