Generics
Generics
Generics
(polymorphisme paramétrique)
en Java
Philippe Genoud
dernière mise à jour : 20/12/2017
‒…
• Comment procéder ?
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.
T 3 val) {
public void ajouter(Object
Element <T> elt = new Element <T>(val); class Element <T>
{<T> {{
if (estVide()) { 4 1
tête = queue = elt; private final Object
T value;
} else { private Element next;
queue.inserer(elt); Dans le code du type
queue = elt; T 3 val) {
Element(Object
}
générique, le paramètre de this.value = val;
} type peut être utilisé next = null;
comme les autres types : }
public boolean estVide() { 4
return tête == null; void inserer(Element <T> elt) {
pour déclarer des variables 1 this.next = elt;
}
}
2
public Object
T retirer() { pour définir le type de
Object
1 T val = null; T 2 getValue() {
public Object
if (!estVide()) {
retour 2 ou le type des return value;
val = tête.getValue(); paramètres de méthodes 3 }
tête = tête.getNext();
} comme argument d'autres public Element <T> getNext() {
return val; types génériques 4 return next;
} }
} }
• 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 Paire<String, Forme> c1 = new Paire<>("Cercle 1",new Cercle(0,0,10));
ou abstraites)
‒ des interfaces Paire<String, IDessinable> c2 = new Paire<>("Visage 1", new VisageRond());
Implémentation
...
• 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)
T x1;
T[] tab;
static T x2;
void method3(...) {
...
x1 = new T();
tab = new T[10] ;
}
...
}
• 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() ); true
System.out.println( c1 instanceof Paire ); true
System.out.println( p1 instanceof Paire ); true
- 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;
...
String s2 = c2.getSecond(); ClassCastException: Personne cannot be cast to String
Arrays propose
des méthodes
exemple : méthode copyOf de la classe java.util.Arrays utilitaires pour
permet de créer un tableau d’une taille donnée qui est une copie faire de
recherches, tris,
d’un tableau passé en paramètre copies de
tableaux….
newLength - 1
int
0
... Tableau
original
length <= newLength
newLength - 1
le même type tableau que pour original 0
...
Les autres éléments
sont « mis à zero »
• 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 { /* ... */ } si l'une des bornes est une classe (et il ne peut y en avoir
interface B { /* ... */ } qu'une seule, car héritage simple en Java), celle-ci doit être
interface C { /* ... */ } spécifiée en premier
Couple(T val1, T val2) { Il est possible de sous typer une classe ou une interface
super(val1, val2); générique en l'étendant ou en l'implémentant
}
l'inverse est possible, une classe générique peut hériter d'une classe
• situations incorrectes non générique class ClasseB<T> extends ClasseC{ … }
‒ les contraintes doivent être exprimées class ClasseB<T> extends ClasseA<T extends TypeC>{
sur les paramètre de la classe dérivée … }
<<interface>>
Iterable<E>
<<interface>>
Collection<E>
AbstractSet<E> AbstractList<E>
<<interface>> <<interface>> <<interface>>
Set<E> Queue<E> List<E>
TreeSet<E>
ArrayDeque<E>
<<interface>>
NavigableSet<E>
// Collection Views
public Set<K> keySet();
public Collection<V> values(); Map.Entry définit un couple
public Set<Map.Entry<K,V>> entrySet(); clé valeur
AbstractMap<K,V>
Classe présente
dans JDK 1.1
"Refactorisée"
pour s'intégrer au
<<interface>> HashMap<K,V> framework des
collections
Map<K,V>
HashTable<K,V>
TreeMap<K,V>
<<interface>>
SortedMap<K,V> LinkedHashMap<K,V>
Collision
k2.hashCode() == k1.hashCode()
En fait la Map n'est pas une table de Map.Entry
v2
mais une table de listes de Map.Entry k2.hashCode() k2
k1.hashCode()
k1 v1
Une bonne fonction de hashCode doit assurer une bonne
dispersion des clés en minimisant les collisions
map.put(p1,num); F
map.put(new Personne("DUPONT","Jean","Mr"),
new NumTel("4234548",'D')); .. p1
String prenom =
LectureClavier.lireChaine("Prénom : "); key value
String civilite =
LectureClavier.lireChaine("Civilité : "); 4234548
Personne p2 = new Personne(nom,prenom,civilite); T
DUPONT
hashCode() doit TOUJOURS être Jean
p2.hashCode()
redéfinie en cohérence avec equals() p2
Mr
on pourrait écrire ainsi ajouter un enseignant à la liste des étudiants lesPersonnes add un Enseignant
mais alors l'exécution de
lesEtudiants.get(lesEtudiants.size()-1);
donnerait une erreur de transtypage C'est pourquoi le compilateur interdit l'instruction 2
Collection<Personne> Collection<Object>
Collection<Object>
n'est pas une super classe List<Personne> List<Object>
de n'importe quel type de
Collection
ArrayList<Personne> ArrayList<Object>
‒ de manière générale
Pourtant il est toujours possible d'utiliser un objet de type ClasseG<ClasseB>
ClasseA ClasseG<ClasseA> 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
ClasseB ClasseG<ClasseB>
notion de Joker (wildcard)
exemple inspiré de : Java tutorial, Lesson Generics de Gilad Bracha https://docs.oracle.com/javase/tutorial/extra/generics/wildcards.html
• 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
‒ 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)
‒ à 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 List<Personne> lp = new ArrayList<>();
lp.add(new Personne("DUPONT","Jean"));
l'objet de type générique avec une valeur
lp.add(new Etudiant("DUPOND", "Marcel"));
dont le type correspond à la borne List<? super Etudiant> l1 = lp;
inférieure). Par contre un transtypage est erreur de
l1.add(new Etudiant("DURAND", "Anne")); compilation
nécessaire pour les appels de méthodes Personne p = (Personne) l1.get(0);
dont le type de retour correspond au joker. l1.add(new Personne("DURANT", "Sophie"));