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 1
2
3
4
5
6
7
8
9
10
11
12
13 | import java.util.ArrayList;
public class Test {
public static void main(String[] args) {
ArrayList<Voiture> listVoiture = new ArrayList<Voiture>();
ArrayList<VoitureSansPermis> listVoitureSP = new ArrayList<VoitureSansPermis>();
listVoiture = listVoitureSP;//Interdit ! ! ! !
}
}
|
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;
public class Test {
public static void main(String[] args) {
//Voici un ArrayList n'acceptant que des instances de Voiture ou de ses sous-classes
ArrayList<? extends Voiture> listVoitureSP = new ArrayList<VoitureSansPermis>();
}
}
|
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 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 | import java.util.ArrayList;
public class Test {
public static void main(String[] args) {
ArrayList<? extends Voiture> listVoitureSP = new ArrayList<VoitureSansPermis>();
afficher(listVoitureSP);
}
/**
* Méthode générique !
* @param <T>
* @param list
*/
static void afficher(ArrayList<? extends Voiture> list){
for(Voiture v : list)
System.out.println(v.toString());
}
}
|
Eh ! Attends, on a voulu ajouter des objets dans notre collection et le programme ne compile plus !
Oui, alors, ce que je ne vous avait pas dis, c'est que, dès que vous utilisez le wildcard combiné avec le mot clé
extends, vos listes seront verrouillées en insertion :
Elles se transforment en collections en lecture seule...
Pourquoi ça ?
En fait, il faut déjà savoir que c'est à la compilation du programme que Java ne vous laisse pas faire.
Le verrou vient du fait que, vu que le wildcard signifie "
tout objet" combiné avec
extends signifiant "
héritant", au moment de la compilation, Java n'a aucune idée de l'objet qu'on vient d'assigner à notre collection : les concepteurs ont donc préféré bloquer ce mode d'utilisation.
Par contre, ce type d'utilisation fonctionne à merveille pour la lecture :
Code : Java 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33 | import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args){
//Liste de voiture
ArrayList<Voiture> listVoiture = new ArrayList<Voiture>();
listVoiture.add(new Voiture());
listVoiture.add(new Voiture());
ArrayList<VoitureSansPermis> listVoitureSP = new ArrayList<VoitureSansPermis>();
listVoitureSP.add(new VoitureSansPermis());
listVoitureSP.add(new VoitureSansPermis());
affiche(listVoiture);
affiche(listVoitureSP);
}
/**
* Avec cette méthode, on accepte aussi bien les collections de Voiture
* que les collection de VoitureSansPermis
* @param list
*/
static void affiche(List<? extends Voiture> list){
for(Voiture v : list)
System.out.print(v.toString());
}
}
|
Avant que vous ne posiez la question : NON ! Déclarer la méthode comme ceci
affiche(List<Voiture> list) ne vous permet pas de parcourir des listes de
VoitureSansPermis, même si celle-ci hérite de la classe
Voiture.
Les méthodes déclarées avec un type générique sont verrouillées afin de n'être utilisées qu'avec ce type bien précis, toujours pour les mêmes raisons que ci-dessus !
Pfiou ! C'est bien compliqué tout ça...
Attendez, ce n'est pas encore fini. Nous avons vu comment élargir le contenu de nos collections (pour la lecture), nous allons voir comment restreindre les collections acceptées par nos méthodes.
La méthode :
Code : Java | static void affiche(List<? extends Voiture> list){
for(Voiture v : list)
System.out.print(v.toString());
}
|
Autorise un objet de type
List de n'importe quel type dont
Voiture est la super classe.
L'instruction suivante signifie :
La méthode autorise un objet de type
List de n'importe quel super classe de la classe
Voiture,
Voiture y compris.
Code : Java | static void affiche(List<? super Voiture> list){
for(Object v : list)
System.out.print(v.toString());
}
|
Ce code fonctionne donc parfaitement :
Code : Java 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 | public static void main(String[] args){
//Liste de voiture
List<Voiture> listVoiture = new ArrayList<Voiture>();
listVoiture.add(new Voiture());
listVoiture.add(new Voiture());
ArrayList<Object> listVoitureSP = new ArrayList<Object>();
listVoitureSP.add(new Object());
listVoitureSP.add(new Object());
affiche(listVoiture);
}
/**
* Avec cette méthode, on accepte aussi bien les collections de Voiture
* que les collection d' Object : super classe de toutes les classes
* @param list
*/
static void affiche(List<? super Voiture> list){
for(Object v : list)
System.out.print(v.toString());
}
|
Je conçois bien que ceci est un peu ardu à comprendre... Mais vous en aurez sûrement besoin dans une de vos prochaines applications !
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.