Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                

Generics

Télécharger au format pdf ou txt
Télécharger au format pdf ou txt
Vous êtes sur la page 1sur 38

Généricité

(polymorphisme paramétrique)
en Java
Philippe Genoud
dernière mise à jour : 20/12/2017

© Philippe GENOUD UJF Janvier 2018 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 2018 2


Généricité Motivations
• 1ère solution : écrire une classe pour chaque type de valeur que l'on
peut mettre dans la file class ElementInt {

private final int value;


public class FileAttenteInt {
private ElementInt next;
private ElementInt tête = null;
ElementInt(int val) {
private ElementInt queue = null;
this.value = val;
next = null;
public void ajouter(int val) {
}
ElementInt elt = new ElementInt(val);
if (estVide()) {
void inserer(ElementInt elt) {
tête = queue = elt;
this.next = elt;
} else {
}
queue.inserer(elt);
queue = elt;
int getValue() {
}
return value;
}
}
public boolean estVide() {
ElementInt getNext() {
return tête == null;
return next;
}
}
if (this.estVide()) {
}
public int retirer() {
int val = -1; throw new IllegalOpException("liste vide");
if (!estVide()) { }
int val = tête.getValue(); public class IllegalopException
val = tête.getValue();
tête = tete.getNext() extends RuntimeException {
tête = tête.getNext(); return val
}
public IllegalOpException(String mess) {
return val;
super(mess);
} }
} }

© Philippe GENOUD UJF Janvier 2018 3


Généricité Motivation
• 1ère solution : écrire une classe pour chaque type de valeur que l'on
peut mettre dans la file class ElementPersonne {

private final Personne value;


public class FileAttentePersonne {
private ElementPersonne next;
private ElementPersonne tête = null;
ElementPersonne(Personne val) {
private ElementPersonne queue = null;
this.value = val;
next = null;
public void ajouter(Personne val) {
}
ElementPersonne elt = new ElementPersonne(val);
if (estVide()) {
void inserer(ElementPersonne elt) {
tête = queue = elt;
this.next = elt;
} else {
}
queue.inserer(elt);
queue = elt;
Personne getValue() {
}
return value;
}
}
public boolean estVide() {
ElementPersonne getNext() {
return tête == null;
return next;
}
}
}
public Personne retirer() {
Personne val = null;
if (!estVide()) {
val = tête.getValue();
• Duplication du code  source d'erreurs à l'écriture
tête = tête.getNext(); et lors de modifications du programme
}
return val; • Nécessité de prévoir toutes les combinaisons
}
} possibles pour une application

© Philippe GENOUD UJF Janvier 2018 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 final Object value;


private Element next;
private Element tête = null;
private Element queue = null; Element(Object val) {
this.value = val;
public void ajouter(Object val) { next = null;
Element elt = new Element(val); }
if (estVide()) {
tête = queue = elt; void inserer(Element elt) {
} else { this.next = elt;
queue.inserer(elt); }
queue = elt;
} public Object getValue() {
} return value;
}
public boolean estVide() {
return tête == null; public Element getNext() {
} return next;
}
public Object retirer() { }
Object val = null;
if (!estVide()) {
val = tête.getValue();
une file d'attente FileAttente f1 = new FileAttente();
de personnes f1.add(new Personne(...));
tête = tête.getNext();
}
return val;
une file d'attente FileAttente f2 = new FileAttente();
} f2.add(new Event(...));
} d'événements

© Philippe GENOUD UJF Janvier 2018 5


Généricité Motivation
• Mais ...
FileAttente f1 = new FileAttente();
f1.ajouter(new Personne(...));  obligation pour le programmeur
...
// on veut récupérer le nom de la personne d'effectuer un transtypage lorsqu’il
// en tête de file. accède aux éléments de la file d'attente
String nom = (Personne)(f1.retirer() ).getNom();
Transtypage
Object

f1.ajouter("Hello");  pas de contrôle sur les valeurs


String nom = (Personne) (f1.retirer()).getNom(); 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 2018 6


Généricité Motivation
• Mais ...
Mais ça, c'était avant…
FileAttente f1 = new FileAttente();
f1.ajouter(new Personne(...));  obligation pour le programmeur
...
// on veut récupérer le nom de la personne d'effectuer un transtypage lorsqu’il
// en tête de file. accède aux éléments de la file d'attente
String nom = (Personne)(f1.retirer() ).getNom();
Transtypage
Object

f1.ajouter("Hello");  pas de contrôle sur les valeurs


String nom = (Personne) (f1.retirer()).getNom(); rangées dans la file d'attente
ClassCastException

depuis la version 1.5 de java (Tiger)


cela n’est qu’un mauvais souvenir
grâce à l’introduction (enfin !) de
 code lourd, moins lisible, plus difficile à maintenir la généricité dans le langage Java.
 risques d’erreurs d’exécution.

© Philippe GENOUD UJF Janvier 2018 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 T
Element
- next
-value : T
0..1
Element(val : T)
getValue() : T
getNext() : Element<T>

© Philippe GENOUD UJF Janvier 2018 8


Généricité Exemple générique simple
Déclaration d'une classe

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> { paramètre de type :représente un type inconnu au


moment de la compilation
private Element <T> tête = null;
private Element <T>4 queue = null;

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;
} }
} }

© Philippe GENOUD UJF Janvier 2018 9


Généricité Utilisation d'une classe
Exemple générique simple
Lors de l’instanciation de FileAttente<T> le type formel T est remplacé par un type
(Classe ou Interface) existant
le type est passé en argument
une file d'attente FileAttente<Personne> f1 = new FileAttente<Personne>(); Diamond
de personnes f1.add(new Personne(...)); on n'est pas
obligé de
mentionner les
une file d'attente FileAttente<Event> f2 = new FileAttente<>(); arguments de
d'événements f2.add(new Event(...)); type dans le
constructeur

sans généricité (< JDK 5) avec généricité (JDK 5+)


FileAttente f1 = new FileAttente(); f1.ajouter(new Personne(...));
f1.ajouter(new Personne(...)); ...
... // on veut récupérer le nom de la personne
// on veut récupérer le nom de la personne // en tête de file.
// en tête de file. String nom = f1.retirer().getNom();
String nom = (Personne)( f1.retirer()) .getNom();
Plus de transtypage
Transtypage obligatoire
Erreur détectée dès la compilation
Type incorrect
f1.ajouter("Hello"); f1.ajouter("Hello");
String nom = (Personne) (f1.retirer()).getNom(); String nom = f1.retirer().getNom();
ClassCastException Erreur à l’exécution

 La généricité simplifie la programmation, évite de dupliquer du code


et le rend plus robuste
© Philippe GENOUD UJF Janvier 2018 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 class Paire<T1, T2> { public interface Comparable<T> { T - the type of
int compareTo(T o); objects that this
private final T1 first; } object may be
private final T2 second; compared to

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 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());

‒ 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 2018 11


Généricité

Implémentation

© Philippe GENOUD UJF Janvier 2018 12


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> { toutes les informations public class Paire {
private final T1 first; de type placées entre private final Object first;
private final T2 second; chevrons sont effacées private final Object second;

public Paire(T1 first, T2 second) { public PaireO(Object first, Object second) {


this.first = first; this.first = first;
this.second = second; this.second = second;
Les variables de type }
}
sont remplacées par
public T1 getFirst() { Object (en fait leur public Object getFirst() {
return first; borne supérieure en return first;
} cas de généricité }
contrainte)
public T2 getSecond() { public Object getSecond() {
return second; return second;
} }
javac }
}
Le type Paire<T1,T2> a été
remplacé par un type brut (raw type)
Paire<String, String> c1; en substituant Object à T1 et T2
c1 = new Paire<>("Hello","World");
insertion d'opérations de Paire.class
transtypage si nécessaire
String s = c1.getFirst(); String s = (String) c1.getFirst();

Personne p = c1.getFirst(); javac


rejeté dès la compilation : une String
n'est pas une Personne

© Philippe GENOUD UJF Janvier 2018 13


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 , raw type) partagée par toutes
les instanciations du type générique En C++
création de
code source
Paire<String,String> c1; spécifique pour
Paire<Personne,Personne> p1; chaque valeur
c1 = new Paire<>("Hello","World"); de type
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
‒ les membres statiques d'une classe paramétrée sont partagés par toutes les instanciations de
public class Paire<T1,T2> {
celle-ci
private static int nbInstances = 0;
private final T1 first; Paire<String,String> c1;
private final T2 second; Paire<Personne,Personne> p1;
c1 = new Paire<>("Hello","World");
public Paire(T first, T2 second) { p1 = new Paire<>(new Personne("DURAND", "Sophie"),
nbInstances++ new Personne("DUPONT", "Jean"));
this.first = first; System.out.println( Paire.getNbInstances() );
this.second = second;
}  2
public static getNbInstances() {
return nbInstances;
}

...

© Philippe GENOUD UJF Janvier 2018 14


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 2018 15


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() );  true
System.out.println( c1 instanceof Paire );  true
System.out.println( p1 instanceof Paire );  true

‒ 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;
...
String s2 = c2.getSecond(); 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 2018 16


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

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

T[] un type tableau


0
...
length > newLength length - 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 »

Quelle signature pour cette méthode ?

© Philippe GENOUD UJF Janvier 2018 17


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( TypeTableau original, int newLength)

public int[] copyOf(int[] original, int newLength)


Pour les types simples :
surcharge de la méthode 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(T[]original, int newLength)


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); argument de type
peut être ignoré
à l'invocation…
Personne[] tab2 = Arrays.copyOf(tab1, 100); mais attention
on verra plus
tard les consé-
quences
© Philippe GENOUD UJF Janvier 2018 18
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 Méthode générique dans une
classe non générique. classe générique.
public class Class1{ public class Paire<T1, T2> {

private int x; private final T1 first;


... private final T2 second;

public Class1(...) { public Paire(T1 first, T2 second) {


... this.first = first;
} this.second = second;
}
public <T1,T2> T1 methodX(T1 p1, T2 p2) {
... public T1 getFirst(){
} return first;
}
... Le (les) paramètre(s) de type de
} public T2 getSecond(){ la méthode générique ne fait
return second; (font) pas partie des paramètres
} de type de la classe.

teste si la seconde composante de la paire (this) est


public <T3> boolean sameScnd(Paire<T1,T3> p) {
égale à la seconde composante d'une autre paire return getSecond().equals(p.getSecond());
dont la première composante n'est pas forcément }
du même type que celle this
}

© Philippe GENOUD UJF Janvier 2018 19


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.
la classe couple ne pourra être instanciée qu'avec le
type Personne ou un type dérivé
Exemple avec une classe générique Couple<Personne> cp;
Couple<Etudiant> ce;
public class Couple<T extends Personne> { erreur de compilation
Couple<Point> cpt; un point n'est pas une
private final T first; personne
private final T second;

public Couple(T first, T second) { A la compilation, le mécanisme


this.first = first; d'effacement de type consiste à
this.second = second; remplacer T par sa borne supérieure
}
public class Couple {
type brut (raw type)
public T getFirst() { return first; }
private final Personnefirst;
private final Personne second;
public T getSecond() { return second; }
public Couple(Personne first, 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é ce.getFirst(); (Personne) ce.getFirst();

© Philippe GENOUD UJF Janvier 2018 20


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 { /* ... */ } 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

class D <T extends A & B & 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)
class D <T extends B & 1 & C> {
...
}

© Philippe GENOUD UJF Janvier 2018 21


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 T first;


private final T2 second; private final T second;

public Paire(T1 first, T2 second) { public Couple(T first, T second) {


this.first = first; this.first = first;
this.second = second; this.second = second;
} }

public T1 getFirst(){ public T getFirst() { return first; }


return first;
} public T getSecond() { return second; }

public T2 getSecond(){ public int getAgeMoyen() {


return second; return (first.getAge() +
} second.getAge()) / 2;
}
} }

public class Couple<T extends Personne> extends Paire<T, T> {

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
}

public int getAgeMoyen() {


return (getFirst().getAge() + getSecond().getAge()) / 2;
}}

© Philippe GENOUD UJF Janvier 2018 22


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 class ClasseB<T> extends ClasseA<T>{


… }
de la classe de base
‒ en ajoutant de nouveaux paramètres class ClasseB<T,U> extends ClasseA<T>{
de type … }

‒ en introduisant des contraintes sur un


class ClasseB<T extends TypeC> extends ClasseA<T>{
ou plusieurs des paramètres de la … }
classe de base
‒ en spécifiant une instance particulière class ClasseB extends ClasseA<String>{
… }
de la classe de base
class ClasseB<T> extends ClasseA<String>{
… }

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{ … }

‒ ClasseB doit disposer au moins du class ClasseB extends ClasseA<T>{


paramètre T … }

‒ les contraintes doivent être exprimées class ClasseB<T> extends ClasseA<T extends TypeC>{
sur les paramètre de la classe dérivée … }

© Philippe GENOUD UJF Janvier 2018 23


Généricité héritage de classes génériques
• L'API des collections (java.util) s'appuie sur la généricité
Les collections
AbstractCollection<E>

<<interface>>
Iterable<E>

<<interface>>
Collection<E>

AbstractSet<E> AbstractList<E>
<<interface>> <<interface>> <<interface>>
Set<E> Queue<E> List<E>

HashSet<E> ArrayList<E> Vector<E>


AbstractQueue<E>
AbstractSequentialList<E>
LinkedHashSet<E>
<<interface>> <<interface>> LinkedList<E>
EnumSet<E extends Enum<E>> SortedSet<E> Deque<E>
PriorityQueue<E>

TreeSet<E>

ArrayDeque<E>
<<interface>>
NavigableSet<E>

© Philippe GENOUD UJF Janvier 2018 24


Généricité Interface Map
Les Maps (tables associatives)
public interface Map<K,V> {
// Basic Operations Associe une valeur à une clé
V put(K key, V value);
V get(Object key);
On accède directement à la
Object remove(Object key); valeur à partir de la clé
boolean containsKey(Object key);
boolean containsValue(Object value);
int size();
Les clés forment un
boolean isEmpty(); ensemble (chaque clé
est unique)
// Bulk Operations
void putAll(Map t); Les valeurs constituent
une collection
void clear();

// 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

// Interface for entrySet elements


public interface Entry<K,V> {
K getKey();
V getValue();
V setValue(Object value);
}
} © Philippe GENOUD UJF Janvier 2018 25
Généricité Map : Les implémentations

Les Maps (tables associatives)

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>

IndentityHashMap<K,V> Conserve ordre


d’insertion des
Les clés doivent implémenter éléments
interface Comparable ou un
Comparator doit être fourni à Utilise == plutôt
la création de la Map que equals pour
comparer les clés

© Philippe GENOUD UJF Janvier 2018 26


Généricité Map : insertion
Map : abstraction de structure de données
avec adressage dispersé (hashCoding) map Map
1
La Map peut être vue comme une table de couples clé/valeur
key value
(Map.Entry) 15
11
Map<String,Point> map = new HashMap<>(); 2
pt1
Ajouter une entrée à la Map
Sommet A
String s1 = new String("Sommet A");
Point pt1 = new Point(15,11);
s1
..
map.put(s1,pt1); . s1.hashCode()

1) Créer un objet Map.Entry référençant s1 et pt1


2) Ranger cette Map.Entry dans la table à une position qui dépend
de la clé (s1)
Cette position est calculée en envoyant le message hashCode() à l'objet clé (s1)

Tout objet possède une méthode hashCode()

© Philippe GENOUD UJF Janvier 2018 27


Généricité Map : hashCode

© Philippe GENOUD UJF Janvier 2018 28


Généricité Map : hashCode
map.put(k1,v1);
Map
map.put(k2,v2);

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

Retrouver une valeur stockée dans la Map


..
map.get(k2); k2.hashCode() .
1 2 3
key value key value

1 k2.hashCode()  position de la clé dans la Map

2 Parcours séquentiel de la liste Map.Entry à la


recherche d'une clé k telle que k.equals(k2)

3 Si k est trouvée, retour de la valeur associée dans la Map.entry


Sinon retour de null

k1.hashCode()

k1 v1
Une bonne fonction de hashCode doit assurer une bonne
dispersion des clés en minimisant les collisions

© Philippe GENOUD UJF Janvier 2018 29


Généricité Map: hashCode
num1
map Map
Map<Personne,NumTel> map = new HashMap<>();
Personne p1 = new Personne("DURAND","Sophie","Mlle");
key value
NumTel num = new NumTel("1234547",'F'); 1234547

map.put(p1,num); F

NumTel num1 = map.get(p1);


num
System.out.println(num1);  1234547 (F) DURAND
Sophie
Mlle

map.put(new Personne("DUPONT","Jean","Mr"),
new NumTel("4234548",'D')); .. p1

String nom = LectureClavier.lireChaine("Nom : "); . p1.hashCode()

String prenom =
LectureClavier.lireChaine("Prénom : "); key value
String civilite =
LectureClavier.lireChaine("Civilité : "); 4234548
Personne p2 = new Personne(nom,prenom,civilite); T

NumTel num2 = map.get(p2); DUPONT

System.out.println(num2);  null ??? hashCode() Jean


Mr

p2 est un objet distinct de la clé (même si il représente la num2 null


même personne) et la fonction hashCode() héritée de
Object base son calcul sur la référence (adresse p2.hashCode()
DUPONT
Jean
mémoire de cet objet) Mr
p2
 il faut redéfinir hashCode() dans Personne
© Philippe GENOUD UJF Janvier 2018 30
Généricité Map : hashCode
public class Personne {
...
public boolean equals(Object o) {
Map
if (! (o instanceof Personne) )
return false; key value
1234547
Personne p = (Personne) o; F

return civilite_ == p.civilite_&&


nom_.equals(p.nom_) && DURAND
prenom_.equals(p.prenom_); Sophie
Mlle
}
ATTENTION : les String sont des
objets, bien penser à utiliser equals et ..
non pas == pour comparer des chaînes .
public int hashCode() {
String nomPlusPrenom = nom_ + prenom_; key value
return nomPlusPrenom.hashCode() + civilite_;
} 4234548
T
}
DUPONT
hashCode() Jean
Personne p2 = new Personne("DUPONT","Jean","Mr"); Mr

NumTel num2 = map.get(p2);


System.out.println(num2);  4234548 (T) num2

DUPONT
hashCode() doit TOUJOURS être Jean
p2.hashCode()
redéfinie en cohérence avec equals() p2
Mr

© Philippe GENOUD UJF Janvier 2018 31


Généricité

© Philippe GENOUD UJF Janvier 2018 32


Généricité sous-typage
<<interface>>
Collection<E> ArrayList<E> implémente List<E> Collection<Personne>
List<E> étend Collection<E>
<<interface>> List<Personne>
List<E>
 ArrayList<Personne> est un sous
type de List<Personne> qui est un sous
type de Collection<Personne> ArrayList<Personne>
LinkedList<E>

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>(); 1
lesEtudiants des Etudiants
...
List<Personne> lesPersonnes = lesEtudiants; 2
lesPersonnes.add(new Enseignant("DUPOND","Jean"));

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

© Philippe GENOUD UJF Janvier 2018 33


Généricité Jokers (wildcards)
• pb: écrire une méthode qui affiche tous les éléments d'une collection*
‒ approche naïve void printCollection(Collection<Object> c) {
for (Object e : c) {
System.out.println(e);
}
}

‒ pourquoi cette solution ne List<Personne> lesPersonnes = new ArrayList<Personne>();


marche t'elle pas ? ...
printCollection(lesPersonnes);
incompatibles types: List<Personne> cannot be converted to Collection<Object>

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

© Philippe GENOUD UJF Janvier 2018 34


Généricité Joker simple

Quelle supertype pour les types ? : Joker simple


Collection<?>
??? Collection d'un type inconnu
de collections ?

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<?>
printCollection(Collection<Object>
c) { c) {
for (Object e : c) {
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 FileAttente<Personne> fp = new FileAttente<>();


les modifications d'un objet de type générique Personne p = new Personne("DUPONT", "Jean");
ClasseG<?> qui sont interdites mais tout appel fp.ajouter(p);
FileAttente<?> f = fp;
de méthode recevant un argument de type f.contient(p);
correspondant à ?
© Philippe GENOUD UJF Janvier 2018 35
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; erreur de compilation
l1.add(new Etudiant("DURAND","Anne"));

© Philippe GENOUD UJF Janvier 2018 36


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
Il n'est pas possible
de spécifier
quel type pour uneListe ?
simultanément une
/**
Object borne supérieure et
* ajoute cet étudiant à une liste une borne
* @param uneListe la liste à laquelle cet etudiant est ajouté inférieure
Personne */
void ajouterA(List<? super Etudiant> uneListe){ une liste peut être:
• une liste d'Etudiants List<Etudiant>
uneListe.add(this); • une liste de Personnes List<Personne>
Etudiant Enseignant } • 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 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"));

© Philippe GENOUD UJF Janvier 2018 37


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 2018 38

Vous aimerez peut-être aussi