Généricité (polymorphisme paramétrique) en Java
© Philippe GENOUD
UJF
Janvier 2015
1
Généricité Motivations
• Supposons que l'on développe du code pour gérer une file d'attente (FIFO First In First Out) et que l'on veuille utiliser ce code pour
‒ une file d'entiers ‒ une file de chaînes de caractères (String) ‒ une file d'objets Personne ‒ …
• Comment procéder ?
© Philippe GENOUD
UJF
Janvier 2015
2
Généricité Motivations
• 1ère solution : écrire une classe pour chaque type de valeur que l'on
peut mettre dans la file
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; } }
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; } }
© Philippe GENOUD
UJF
Janvier 2015
3
Généricité Motivation
• 1ère solution : écrire une classe pour chaque type de valeur que l'on
peut mettre dans la file
public class FileAttentePersonne {
private ElementPersonne tête = null; private ElementPersonne queue = null;
public void ajouter(Personne val) { ElementPersonne elt = new ElementPersonne(val); if (estVide()) { tête = queue = elt; } else { queue.inserer(elt); queue = elt; } }
public boolean estVide() { return tête == null; }
public Personne retirer() { Personne val = null; if (!estVide()) { val = tête.getValue(); tête = tête.getNext(); } return val; } }
class ElementPersonne {
private final Personne value; private ElementPersonne next;
ElementPersonne(Personne val) { this.value = val; next = null; }
void inserer(ElementPersonne elt) { this.next = elt; }
Personne getValue() { return value; }
ElementPersonne getNext() { return next; } }
• Duplication du code source d'erreurs à l'écriture
et lors de modifications du programme
• Nécessité de prévoir toutes les combinaisons
possibles pour une application
© Philippe GENOUD
UJF
Janvier 2015
4
Généricité Motivation • 2ème solution : utiliser un type "universel", Object en Java
‒ toute classe héritant de Object il est possible d'utiliser FileAttente pour
stocker n'importe quel type d'objet
class Element {
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; } }
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; } }
une file d'attente de personnes
une file d'attente d'événements
FileAttente f1 = new FileAttente(); f1.add(new Personne(...));
FileAttente f2 = new FileAttente(); f2.add(new Event(...));
© Philippe GENOUD
UJF
Janvier 2015
5
Généricité
Motivation
• Mais ...
FileAttente f1 = new FileAttente(); f1.ajouter(new Personne(...)); ... // on veut récupérer le nom de la personne // en tête de file. String nom = f1.retirer() .getNom();
(Personne)( )
Transtypage
Object
obligation pour le programmeur d'effectuer un transtypage lorsqu’il accède aux éléments de la file d'attente
f1.ajouter("Hello"); String nom = (Personne) (f1.retirer()).getNom();
pas de contrôle sur les valeurs rangées dans la file d'attente
ClassCastException
code lourd, moins lisible, plus difficile à maintenir risques d’erreurs d’exécution.
© Philippe GENOUD
UJF
Janvier 2015
6
Généricité
Motivation
• Mais ...
Mais ça, c'était avant…
FileAttente f1 = new FileAttente(); f1.ajouter(new Personne(...)); ... // on veut récupérer le nom de la personne // en tête de file. String nom = f1.retirer() .getNom();
(Personne)( )
obligation pour le programmeur d'effectuer un transtypage lorsqu’il accède aux éléments de la file d'attente
Transtypage
Object
f1.ajouter("Hello"); String nom = (Personne) (f1.retirer()).getNom();
pas de contrôle sur les valeurs rangées dans la file d'attente
ClassCastException
code lourd, moins lisible, plus difficile à maintenir risques d’erreurs d’exécution.
depuis la version 1.5 de java (Tiger) cela n’est qu’un mauvais souvenir grâce à l’introduction (enfin !) de la généricité dans le langage Java.
© Philippe GENOUD
UJF
Janvier 2015
7
Généricité Principe
• La généricité (ou polymorphisme paramétrique) permet
de faire des abstractions sur les types
‒ utiliser des types (Classes ou Interfaces) pour paramétrer une
définition de classe ou d'interface ou de méthode réutiliser le même code avec des types de données différents.
‒ dans l'exemple précédent le type des éléments serait un paramètre des classes FileAttente et Element
• Présente dans de nombreux langages de programmation avant introduction en
Java: Eiffel, Ada, C++, Haskell, ...
• Présente dans le langage de modélisation UML
Element
T
-value : T
Element(val : T) getValue() : T getNext() : Element<T>
- next
0..1
© Philippe GENOUD
UJF
Janvier 2015
8
Généricité Exemple
Déclaration d'une classe générique simple
Les classes Element et FileAttente sont paramétrées en fonction d’un type formel T
T est utilisé comme type d'une ou plusieurs caractéristiques. Les classes manipulent des informations de type T. Elles ignorent tout de ces informations.
public class FileAttente {
<T> {
private Element tête = null; private Element queue = null;
<T> 4 <T>
paramètre de type :représente un type inconnu au moment de la compilation
3
T
<T>
public void ajouter(Object val) { Element elt = new Element (val); if (estVide()) { tête = queue = elt; } else { queue.inserer(elt); queue = elt; } }
<T> 4
Dans le code du type générique, le paramètre de type peut être utilisé comme les autres types :
public boolean estVide() { return tête == null; }
pour déclarer des variables
class Element {
<T> { <T> {
private final Object value; private Element next;
T
1
3
T
Element(Object val) { this.value = val; next = null; }
void inserer(Element elt) { 1 this.next = elt; }
4 <T>
2
1
T
public Object retirer() { T Object val = null; if (!estVide()) { val = tête.getValue(); tête = tête.getNext(); } return val; } }
pour définir le type de retour ou le type des paramètres de méthodes
2
Publicité
2 public Object getValue() { return value; } 3
T
comme argument d'autres types génériques
4
<T>
public Element getNext() { return next; } }
© Philippe GENOUD
UJF
Janvier 2015
9
Généricité Lors de l’instanciation de FileAttente<T> le type formel T est remplacé par un type (Classe ou Interface) existant
Utilisation d'une classe générique simple
Exemple
le type est passé en argument
une file d'attente de personnes
FileAttente<Personne> f1 = new FileAttente<Personne>(); f1.add(new Personne(...));
une file d'attente d'événements
FileAttente<Event> f2 = new FileAttente<>(); f2.add(new Event(...));
Diamond on n'est pas obligé de mentionner les arguments de type dans le constructeur
sans généricité (< JDK 5)
avec généricité (JDK 5+)
FileAttente f1 = new FileAttente(); f1.ajouter(new Personne(...)); ... // on veut récupérer le nom de la personne // en tête de file. String nom = f1.retirer() .getNom();
(Personne)( ) Transtypage obligatoire
f1.ajouter("Hello"); String nom = (Personne) (f1.retirer()).getNom();
ClassCastException
Erreur à l’exécution
f1.ajouter(new Personne(...)); ... // on veut récupérer le nom de la personne // en tête de file. String nom = f1.retirer().getNom();
Plus de transtypage
Erreur détectée dès la compilation
f1.ajouter("Hello"); String nom = f1.retirer().getNom();
Type incorrect
La généricité simplifie la programmation, évite de dupliquer du code
et le rend plus robuste
© Philippe GENOUD
UJF
Janvier 2015
10
Généricité Types Génériques
Simples
Cas général
•
Type générique : Classe ou interface paramétrée par un ou plusieurs types
classe générique à deux paramètres
interface générique
public interface Comparable<T> { int compareTo(T o); }
T - the type of objects that this object may be compared to
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; } }
•
Instanciation (ou invocation) d'un type générique consiste à valuer les paramètres de type ‒ Les arguments de type (paramètres effectifs) peuvent être :
‒ des classes(concrètes
ou abstraites)
‒ des interfaces
Paire<String, Forme> c1 = new Paire<>("Cercle 1",new Cercle(0,0,10));
Paire<String, IDessinable> c2 = new Paire<>("Visage 1", new VisageRond());
‒ des types paramétrés
Paire<String, Paire<String,Forme>> = new Paire<>("Paire 3",c1);
‒ des paramètres de type
public class FileAttente<E> { private Element<E> tête = null; ...
© Philippe GENOUD
UJF
Janvier 2015
11
Généricité Compilation de code générique
• compilation de code générique basée sur le mécanisme d'effacement (erasure)
public class Paire<T1,T2> { private final T1 first; private final T2 second;
toutes les informations de type placées entre chevrons sont effacées
public class Paire { private final Object first; private final Object second;
public Paire(T1 first, T2 second) { this.first = first; this.second = second; }
public T1 getFirst() { return first; }
public T2 getSecond() { return second; }
}
Les variables de type sont remplacées par Object (en fait leur borne supérieure en cas de généricité contrainte)
javac
public PaireO(Object first, Object second) { this.first = first; this.second = second; }
public Object getFirst() { return first; }
public Object getSecond() { return second; } }
Le type Paire<T1,T2> a été remplacé par un type brut (raw type) en substituant Object à T1 et T2
insertion d'opérations de transtypage si nécessaire
javac
String s = (String) c1.getFirst();
Paire.class
Paire<String, String> c1; c1 = new Paire<>("Hello","World");
String s = c1.getFirst();
Personne p = c1.getFirst();
rejeté dès la compilation : une String n'est pas une Personne
© Philippe GENOUD
UJF
Janvier 2015
12
Généricité conséquences de l'effacement de type
• permet d'assurer la compatibilité ascendante du code. • à l'exécution, il n'existe qu'une classe (le type brut) partagée par toutes les
instanciations du type générique
Paire<String,String> c1; Paire<Personne,Personne> p1; c1 = new Paire<>("Hello","World"); p1 = new Paire<>(new Personne("DURAND", "Sophie"),new Personne("DUPONT", "Jean")); System.out.println( c1.getClass() == p1.getClass() );
true • mais cela induit un certain nombre de limitations
En C++ création de code source spécifique pour chaque valeur de type
‒ les membres statiques d'une classe paramétrée sont partagés par toutes les instanciations de
celle-ci
public class Paire<T1,T2> { private static int nbInstances = 0; private final T1 first; private final T2 second;
public Paire(T first, T2 second) { nbInstances++ this.first = first; this.second = second; }
public static getNbInstances() { return nbInstances; }
...
Paire<String,String> c1; Paire<Personne,Personne> p1; c1 = new Paire<>("Hello","World"); p1 = new Paire<>(new Personne("DURAND", "Sophie"),
new Personne("DUPONT", "Jean"));
System.out.println( Paire.getNbInstances() );
2
© Philippe GENOUD
UJF
Janvier 2015
13
Généricité conséquences de l'effacement de type
• limitations…
‒ les membres statiques d'une classe paramétrée ne peuvent utiliser ses paramètres de type ‒ au sein d'une classe paramétrée on ne peut pas utiliser les paramètre de type pour instancier un
objet (simple ou tableau)
public class C<T> {
T x1; T[] tab; static T x2;
erreurs de compilation
T method1(T param) { ... }
static T method2(T param) { ... }
void method3(...) { ... x1 = new T(); tab = new T[10] ; } ... }
© Philippe GENOUD
UJF
Janvier 2015
14
Généricité conséquences de l'effacement de type
• limitations…
‒ seul le type brut est connu à l'exécution
Paire<String, String> c1 = new Paire<>("Hello","World"); Paire<String, Personne> p1 = new Paire<>("personne 1", new Personne("DURAND", "Sophie")); System.out.println( c1.getClass() == p1.getClass() ); System.out.println( c1 instanceof Paire ); System.out.println( c1 instanceof Paire );
‒ on ne peut utiliser un type générique instancié dans un contexte de vérification de type
if ( c1 instanceof Paire<String, String>) { ... }
- dans un contexte de coercition de type (transtypage) on pourra avoir une erreur à l'exécution mais
pas nécessairement là où on l'attend…
Object obj = p1; Paire<String, String> c2 = (Paire<String, String>) obj; ... System.out.println(c2.getFirst());
ClassCastException: Personne cannot be cast to String
- on ne peut instancier de tableaux d'un type générique
Paire<Personne,Personne>[] duos; duos = new Paire<Personne,Personne>[100] ;
duos = new Paire[100]; // mais utiliser le type brut est possible duos[0] = p1; duos[1] = c1;
© Philippe GENOUD
UJF
Janvier 2015
15
Généricité méthodes génériques
• Méthodes génériques : méthodes qui définissent leur propres paramètres de type. ‒ similaire aux types génériques mais la portée du paramètre de type est limitée à la
méthode où il est déclaré.
‒ méthodes génériques peuvent être définies dans des types non génériques
exemple : méthode copyOf de la classe java.util.Arrays permet de créer un tableau d’une taille donnée qui est une copie d’un tableau passé en paramètre
Arrays propose des méthodes utilitaires pour faire de recherches, tris, copies de tableaux….
T[] un type tableau
int
le même type tableau que pour original
0
Quelle signature pour cette méthode ?
0
newLength - 1
...
length > newLength
...
length <= newLength
Publicité
...
length - 1
Tableau original
newLength - 1
Les autres éléments sont « mis à zero »
© Philippe GENOUD
UJF
Janvier 2015
16
Généricité
méthodes génériques
•
déclaration de copyOf
Il faut que ces types soient les mêmes
public static
TypeTableau
copyOf( original, int newLength)
TypeTableau
Pour les types simples : surcharge de la méthode
public int[] copyOf(int[] original, int newLength)
public float[] copyOf(float[] original, int newLength) ...
public boolean[] copyOf(boolean[] original, int newLength)
Mais pour les types objets ? Object[] Personne[]
définition d'une méthode générique
T : un type java (classe ou interface)
public static
<T>
T[]
copyOf( original, int newLength)
T[]
Comme pour les classes génériques, le paramètre de type doit être déclaré, déclaration entre < > avant le type de retour de la méthode
•
invocation d'une méthode générique
Personne[] tab1 = new Personne[200]; ... Personne[] tab2 = Arrays.<Personne[]>copyOf(tab1, 100);
Personne[] tab2 = Arrays.copyOf(tab1, 100);
© Philippe GENOUD
UJF
Janvier 2015
argument de type peut être ignoré à l'invocation… mais attention on verra plus tard les consé- quences
17
Généricité méthodes génériques
• Dans l'exemple précédent la méthode copyOf est statique, il est aussi possible de
définir des méthodes non statiques génériques.
Méthode générique dans une classe non générique.
Méthode générique dans une classe générique.
public class Class1{
public class Paire<T1, T2> {
private int x; ...
public Class1(...) { ... }
public <T1,T2> T1 methodX(T1 p1, T2 p2) { ... }
... }
teste si la seconde composante de la paire (this) est égale à la seconde composante d'une autre paire dont la première composante n'est pas forcément du même type que celle this
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; }
Le (les) paramètre(s) de type de la méthode générique ne fait (font) pas partie des paramètres de type de la classe.
public <T3> boolean sameScnd(Paire<T1,T3> p) { return getSecond().equals(p.getSecond()); }
}
© Philippe GENOUD
UJF
Janvier 2015
18
Généricité paramétrage contraint (ou borné) • Il existe des situations où il peut être utile d'imposer certaines contraintes sur les
paramètre de type (pour une classe, interface ou une méthode générique).
• généricité contrainte (bounded type parameters)
‒ impose à un argument de type d'être dérivé (sous-classe) d'une classe donnée ou d'implémenter
une ou plusieurs interface.
Exemple avec une classe générique
public class Couple<T extends Personne> {
private final T first; private final T second;
public Couple(T first, T second) { this.first = first; this.second = second; }
public T getFirst() { return first; }
public T getSecond() { return second; }
la classe couple ne pourra être instanciée qu'avec le type Personne ou un type dérivé
Couple<Personne> cp; Couple<Etudiant> ce;
Couple<Point> cpt;
erreur de compilation un point n'est pas une personne
A la compilation, le mécanisme d'effacement de type consiste à remplacer T par sa borne supérieure
public class Couple {
type brut (raw type)
private final Personnefirst; private final Personne second;
public int getAgeMoyen() { return (first.getAge() + second.getAge()) / 2; } }
possibilité d'invoquer les méthodes définies dans la borne du type paramétré
public Couple(Personne first, Personne second) { ... } ... }
ce.getFirst();
(Personne) ce.getFirst();
© Philippe GENOUD
UJF
Janvier 2015
19
Généricité
paramétrage contraint (ou borné)
• borne supérieure peut être soit une classe (concrète ou abstraite) soit une interface
<T1 extends T2>
extends est interprété avec un sens général, si T1 est une classe et T2 une interface extends doit être compris comme implements
• possibilité de définir des bornes multiples (plusieurs interfaces, une classe et une ou
plusieurs interfaces)
<T extends B1 & B2 & B3>
T est un sous type de tous les types listés et séparés par &
Class A { /* ... */ } interface B { /* ... */ } interface C { /* ... */ }
si l'une des bornes est une classe (et il ne peut y en avoir qu'une seule, car héritage simple en Java), celle-ci doit être spécifiée en premier
class D <T extends A & B & C> { ... }
class D <T extends B & 1 & C> { ... }
A la compilation, le mécanisme d'effacement de type consiste à remplacer T par la première de ses bornes supérieures (ici A)
© Philippe GENOUD
UJF
Janvier 2015
20
Généricité héritage de classes génériques
• Dans l'exemple précédent (Couple) n'aurait-il pas été possible de réutiliser le
code de la classe générique Paire<T1,T2> ?
public class Paire<T1, T2> {
public class Couple<T extends Personne> {
private final T1 first; private final T2 second;
private final T first; private final T second;
public Paire(T1 first, T2 second) {
this.first = first; this.second = second;
}
public T1 getFirst(){ return first; }
public T2 getSecond(){ return second; }
}
public Couple(T first, T second) { this.first = first; this.second = second; }
public T getFirst() { return first; }
public T getSecond() { return second; }
public int getAgeMoyen() { return (first.getAge() + second.getAge()) / 2; } }
public class Couple<T extends Personne> extends Paire<T, T> {
Couple(T val1, T val2) { super(val1, val2); }
Il est possible de sous typer une classe ou une interface générique en l'étendant ou en l'implémentant
public int getAgeMoyen() { return (getFirst().getAge() + getSecond().getAge()) / 2; }}
© Philippe GENOUD
UJF
Janvier 2015
21
Généricité héritage de classes génériques
• différentes manières de dériver une classe générique
class ClasseA <T> { … }
‒ en conservant les paramètres de type
de la classe de base
‒ en ajoutant de nouveaux paramètres
de type
‒ en introduisant des contraintes sur un ou plusieurs des paramètres de la classe de base
‒ en spécifiant une instance particulière
de la classe de base
class ClasseB<T> extends ClasseA<T>{ … }
class ClasseB<T,U> extends ClasseA<T>{ … }
class ClasseB<T extends TypeC> extends ClasseA<T>{ … }
class ClasseB extends ClasseA<String>{ … }
class ClasseB<T> extends ClasseA<String>{ … }
• situations incorrectes
l'inverse est possible, une classe générique peut hériter d'une classe non générique class ClasseB<T> extends ClasseC{ … }
‒ ClasseB doit disposer au moins du
paramètre T
‒ les contraintes doivent être exprimées sur les paramètre de la classe dérivée
class ClasseB extends ClasseA<T>{ … }
class ClasseB<T> extends ClasseA<T extends TypeC>{ … }
© Philippe GENOUD
UJF
Janvier 2015
22
Publicité
Généricité héritage de classes génériques
• L'API des collections (java.util) s'appuie sur la généricité
AbstractCollection<E>
<<interface>> Iterable<E>
<<interface>> Collection<E>
AbstractSet<E>
AbstractList<E>
<<interface>> Set<E>
<<interface>> Queue<E>
<<interface>> List<E>
HashSet<E>
LinkedHashSet<E>
AbstractQueue<E>
ArrayList<E>
Vector<E>
AbstractSequentialList<E>
EnumSet<E extends Enum<E>>
<<interface>> SortedSet<E>
<<interface>> Deque<E>
LinkedList<E>
PriorityQueue<E>
TreeSet<E>
<<interface>> NavigableSet<E>
ArrayDeque<E>
© Philippe GENOUD
UJF
Janvier 2015
23
Généricité sous-typage
<<interface>> Collection<E>
<<interface>> List<E>
LinkedList<E>
ArrayList<E> implémente List<E> List<E> étend Collection<E>
Collection<Personne>
ArrayList<Personne> est un sous type de List<Personne> qui est un sous type de Collection<Personne>
List<Personne>
ArrayList<Personne>
List<Personne> lesPersonnes = new ArrayList<Personne>();
Personne
Mais qu'en est-il de ArrayList<Etudiant> , est-ce une liste de Personnes ?
List<Personne> lesPersonnes = new ArrayList<Etudiant>();
Etudiant
Enseignant
incompatibles types: ArrayList<Etudiant> cannot be converted to List<Personnes>
supposons que les types soient compatibles : on pourrait écrire
List<Etudiant> lesEtudiants = new ArrayList<Etudiant>(); ... List<Personne> lesPersonnes = lesEtudiants; lesPersonnes.add(new Enseignant("DUPOND","Jean"));
2
1
lesEtudiants
des Etudiants
on pourrait écrire ainsi ajouter un enseignant à la liste des étudiants mais alors l'exécution de
lesEtudiants.get(lesEtudiants.size()-1);
lesPersonnes
add
un Enseignant
donnerait une erreur de transtypage
C'est pourquoi le compilateur interdit l'instruction
2
© Philippe GENOUD
UJF
Janvier 2015
24
Généricité Jokers (wildcards)
• pb: écrire une méthode qui affiche tous les éléments d'une collection*
‒ approche naïve
‒ pourquoi cette solution ne
marche t'elle pas ?
Collection<Object> n'est pas une super classe de n'importe quel type de Collection
‒ de manière générale
ClasseA
ClasseG<ClasseA>
ClasseB
ClasseG<ClasseB>
void printCollection(Collection<Object> c) { for (Object e : c) { System.out.println(e); } }
List<Personne> lesPersonnes = new ArrayList<Personne>(); ... printCollection(lesPersonnes);
incompatibles types: List<Personne> cannot be converted to Collection<Object>
Collection<Personne>
Collection<Object>
List<Personne>
List<Object>
ArrayList<Personne>
ArrayList<Object>
Pourtant il est toujours possible d'utiliser un objet de type ClasseG<ClasseB> comme un objet de type ClasseG<ClasseA> tant que l'on ne cherche pas à modifier pas sa valeur. pour offrir cette possibilité les concepteurs de Java ont introduit la notion de Joker (wildcard)
exemple inspiré de : Java tutorial, Lesson Generics de Gilad Bracha https://docs.oracle.com/javase/tutorial/extra/generics/wildcards.html
© Philippe GENOUD
UJF
Janvier 2015
25
Généricité Joker simple
Quelle supertype pour les types de collections ?
Collection<?> ???
? : Joker simple Collection d'un type inconnu
Collection<Object>
Collection<String>
...
Collection<Personne>
• En utilisant un Joker pour typer le paramètre Collection de printCollection, il est ensuite
possible d'utiliser cette méthode sur n'importe quel type de collection
void printCollection(Collection<?> c) { void printCollection(Collection<Object> c) { for (Object e : c) { for (Object e : c) { System.out.println(e); System.out.println(e); } } } }
List<Personne> lesPersonnes = new ArrayList<Personne>(); ... printCollection(lesPersonnes);
• Par contre le type des éléments de c étant inconnu, on ne peut lui ajouter des objets
Collection<?> c = new ArrayList<Personne>(); c.add(new Personne("DUPONT", "Jean"));
erreur détectée à la compilation
• De manière générale, ce ne sont pas simplement les modifications d'un objet de type générique ClasseG<?> qui sont interdites mais tout appel de méthode recevant un argument de type correspondant à ?
FileAttente<Personne> fp = new FileAttente<>(); Personne p = new Personne("DUPONT", "Jean"); fp.ajouter(p); FileAttente<?> f = fp; f.contient(p);
© Philippe GENOUD
UJF
Janvier 2015
26
Généricité jokers avec limitations
• comme pour les paramètres de type il est possible d'imposer des restrictions à un
joker
• joker avec borne supérieure <? extends T>
‒ exemple la méthode addAll de Collection<E>
List<Etudiant> lesEtudiants = new ArrayList<>(); ... List<Personne> lesPersonnes = new ArrayList<>(); lesPersonnes.add(new Enseignant("DUPONT","Jean")); lesPersonnes.add(new Etudiant("DURAND","Anne")); lesPersonnes.addAll(lesEtudiants);
une collection de n'importe quel type qui étend le type des éléments de cette collection
‒ comme pour les jokers simples, il n'est pas possible d'invoquer des méthodes ayant un argument correspondant à ? (risques de modifier l'objet de type générique avec une valeur dont le type ne serait pas compatible avec son type effectif)
List<Etudiant> lesEtudiants = new ArrayList<>(); ... List<? Personne> l1 = lesEtudiants; l1.add(new Etudiant("DURAND","Anne"));
erreur de compilation
© Philippe GENOUD
UJF
Janvier 2015
27
Généricité jokers avec limitations
• joker avec borne inférieure <? super T>
‒ de la même manière qu'un joker avec une borne supérieure restreint le type inconnu à un type spécifique ou un sous-type de ce type spécifique, un joker avec une borne inférieure restreint le type inconnu à un type spécifique ou à un super type de ce type spécifique.
‒ exemple: supposons que l'on veuille ajouter à la classe Etudiant une méthode qui permet d'ajouter l'étudiant
à une liste
Object
Personne
quel type pour uneListe ?
/** * ajoute cet étudiant à une liste * @param uneListe la liste à laquelle cet etudiant est ajouté */ void ajouterA( uneListe){
List<? super Etudiant>
Il n'est pas possible de spécifier simultanément une borne supérieure et une borne inférieure
Etudiant
Enseignant
uneListe.add(this); }
une liste peut être: • une liste d'Etudiants List<Etudiant> • une liste de Personnes List<Personne> • une liste d'objets List<Object> mais ne peut pas être une liste d'autre chose List<String>, List<Enseignant> …
‒ à l'inverse des jokers simples ou des jokers avec borne supérieure, il est possible d'invoquer des méthodes ayant
un argument correspondant à la borne inférieure (cela ouvre la possibilité de de modifier l'objet de type générique avec une valeur dont le type correspond à la borne inférieure). Par contre un transtypage est nécessaire pour les appels de méthodes dont le type de retour correspond au joker.
List<Personne> lp = new ArrayList<>(); lp.add(new Personne("DUPONT","Jean")); lp.add(new Etudiant("DUPOND", "Marcel")); List<? super Etudiant> l1 = lp; l1.add(new Etudiant("DURAND", "Anne")); Personne p = (Personne) l1.get(0); l1.add(new Personne("DURANT", "Sophie"));
erreur de compilation
© Philippe GENOUD
UJF
Janvier 2015
28
Généricité références
• The Java Tutorials ‒ Lesson: Generics by Gilad Bracha
https://docs.oracle.com/javase/tutorial/extra/generics/index.html
‒ Lesson: Generics (Updated)
https://docs.oracle.com/javase/tutorial/java/generics/index.html
• Programmer en Java – 9ème Edition Claude Delannoy, Ed. Eyrolles, mai 2014 ‒ chapitre 21 : La programmation générique
• Génériques et collections Java
Maurice Naftalin et Philip Wadler, Ed O’Reilly, juillet 2007
© Philippe GENOUD
UJF
Janvier 2015
29