Programmation et Algorithmique II Ch.13 – Java Collections Framework Bruno Quoitin ([email protected]) (c) 2010, Bruno Quoitin (UMons) 1 Table des Matières 1. Introduction 1. Collection 2. Map 2. Itérateur 3. Conversion de / vers tableau 4. Table de hachage (c) 2010, Bruno Quoitin (UMons) 2 Java Collections Framework ● Introduction – Une Collection est un terme générique désignant une structure de données abstraite destinée à conserver un ensemble d'autres objets (ou les références vers ces objets) – Une collection possède des méthodes qui permettent typiquement – ● d'ajouter un objet à la collection ● de supprimer un objet de la collection ● de récupérer un objet ou tous les objets de la collection. Il existe plusieurs types de collections. Ceux-ci diffèrent selon les opérations disponibles et les contraintes imposées sur l'ensemble d'objets. (c) 2010, Bruno Quoitin (UMons) 3 Java Collections Framework ● Types de collections – Les collections peuvent être classées selon leurs comportements et propriétés. On distingue souvent les collections selon les axes suivants : – Ordre ● – Type des éléments ● – tous les éléments de la collection sont-ils du même type (collections homogènes) / de types différents (collections hétérogènes) ? Duplication ● – la collection garde-t-elle les éléments dans un certain ordre / dans n'importe quel ordre ? les éléments dupliqués sont-ils admis ? Méthodes d'accès ● accès séquentiel / aléatoire ? (c) 2010, Bruno Quoitin (UMons) 4 Java Collections Framework ● Introduction – La bibliothèque Java fournit le Java Collections Framework (JCF), un ensemble important d'interfaces, classes et méthodes destinés à manipuler des collections d'objets. Le JCF comprend – Interfaces de collections – – ● décrivent les services fournis par différentes collections ● p.ex. List, Map, ... Implémentations de collections ● classes concrètes ● p.ex. ArrayList, LinkedList, ... Algorithmes ● ● travaillant sur les collections p.ex. des algorithmes de tri, de recherche, ... (sort, binarySearch, shuffle, ...) (c) 2010, Bruno Quoitin (UMons) 5 Table des Matières 1. Introduction 1. Collection 2. Map 2. Itérateur 3. Conversion de / vers tableau 4. Table de hachage (c) 2010, Bruno Quoitin (UMons) 6 Java Collections Framework ● Interface Collection – Le plus petit élément commun aux collections du JCF est l'interface Collection (java.util). Cette interface définit les opérations liées à la gestion d'un ensemble quelconque d'éléments. public interface Collection<E> extends Iterable<E> { boolean add(E o); boolean addAll(Collection<? extends E> c); void clear(); boolean contains(E o); boolean isEmpty(); Iterator<E> iterator(); boolean remove(E o); boolean removeAll(Collection<?> c); int size(); Object [] toArray(); <T> T[] toArray(T[] a); /* ... */ } (c) 2010, Bruno Quoitin (UMons) 7 Java Collections Framework ● Hiérarchie de classes Collection Collection List Queue AbstractList AbstractQueue Interfaces Set SortedSet AbstractSequentialList HashSet ArrayList (c) 2010, Bruno Quoitin (UMons) Vector Stack LinkedList PriorityQueue Classes concrètes TreeSet Classes abstraites AbstractSet 8 Java Collections Framework ● Hiérarchie de classes Collection – Brève description des classes et de leurs caractéristiques – TreeSet ● – HashSet ● – ensemble, duplication non autorisée, implémentation basée sur table de hachage. ArrayList ● – ensemble trié (ordre défini par Comparable), duplication non autorisée, implémentation basée sur un arbre. séquence, accès aléatoire. LinkedList ● séquence, accès aléatoire possible mais peu performant. ● permet des insertions et suppressions à des positions quelconques (c) 2010, Bruno Quoitin (UMons) 9 Java Collections Framework ● Hiérarchie de classes Collection – Brève description des classes et de leurs caractéristiques (suite) – PriorityQueue ● – Vector ● – permet de retirer efficacement le plus petit élément similaire à ArrayList, thread safe, historique Stack ● pile implémentée sur base de Vector, historique (c) 2010, Bruno Quoitin (UMons) 10 Table des Matières 1. Introduction 1. Collection 2. Map 2. Itérateur 3. Conversion de / vers tableau 4. Table de hachage (c) 2010, Bruno Quoitin (UMons) 11 Java Collections Framework ● Associations (Map) – Le JCF définit également des classes permettant de conserver l'association entre des paires d'éléments clés (keys) et valeurs (values). Il est possible d'accéder aux valeurs d'une association à l'aide de leurs clés (et pas seulement à l'aide d'un index comme dans certaines collections). – Les clés et les valeurs de ces classes peuvent être récupérées indépendamment comme des collections. (c) 2010, Bruno Quoitin (UMons) 12 Java Collections Framework ● Interface Map – L'interface Map (java.util) définit les opérations liées à la gestion d'une association. public interface Map<K,V> { void clear(); boolean containsKey(Object key); boolean containsValue(Object value); Set<Map.Entry<K,V>> entrySet(); V get(Object key); boolean isEmpty(); Set<K> keySet(); V put(K key, V value); void putAll(Map<? extends K, ? extends V> t) ; V remove(Object key); int size(); Collection<V> values(); ... } (c) 2010, Bruno Quoitin (UMons) 13 Java Collections Framework ● Hiérarchie de classes Map Map Interfaces SortedMap Classes abstraites AbstractMap HashMap (trié, basé sur arbre) (non-trié, basé sur table de hachage) (c) 2010, Bruno Quoitin (UMons) Hashtable Classes concrètes TreeMap 14 Java Collections Framework ● Hiérarchie de classes Map – Brève description des classes ● HashMap : une structure de données permettant d'associer des clés à des valeurs ● TreeMap : HashMap dans laquelle les clés sont triées ● Hashtable : similaire à HashMap, thread-safe, historique (c) 2010, Bruno Quoitin (UMons) 15 Table des Matières 1. Introduction 1. Collection 2. Map 2. Itérateur 3. Conversion de / vers tableau 4. Table de hachage (c) 2010, Bruno Quoitin (UMons) 16 Itérateur ● Introduction – Il existe deux modes typiques pour parcourir les éléments d'une collection. – Accès aléatoire ● ● – Chaque élément est accédé indépendamment sur base de son index. Cet accès est possible grâce aux méthodes get() et set() de l'interface List par exemple. Accès séquentiel ● ● ● L'ensemble des éléments de la collection est parcouru. L'ordre dans lequel les éléments sont parcourus dépend tu type de données (p.ex. index croissants pour les listes, ordre de Comparable pour TreeSet, ordre non défini pour HashSet). L'accès séquentiel peut être réalisé en passant par un itérateur. (c) 2010, Bruno Quoitin (UMons) 17 Itérateur ● Principe – Un itérateur (iterator) est un objet capable de parcourir une collection sans en révéler ni la structure interne ni l'implémentation. – Un itérateur repose sur les deux primitives suivantes ● Tester s'il y a encore des éléments à parcourir ● Récupérer le prochain élément à parcourir ● – Ces deux primitives sont typiquement définies dans une interface que de multiples collections peuvent supporter, indépendamment de leur fonctionnement interne. L'itérateur est un design pattern courant. (c) 2010, Bruno Quoitin (UMons) 18 Itérateur ● Principe – Dans le JCF, un itérateur prend la forme d'une implémentation de l'interface Iterator public interface Iterator<T> { public boolean hasNext(); public T next(); } – Indique s'il y a encore des éléments à parcourir. Retourne l'élément suivant à parcourir. Ne doit être appelée que si hasNext() a retourné true. Les classes qui supportent un itérateur implémentent l'interface Iterable. ● Cette interface définit une seule méthode qui permet de récupérer une instance d'itérateur public interface Iterable<T> { public Iterator<T> iterator(); } ● Note : l'interface Collection hérite d'Iterable. (c) 2010, Bruno Quoitin (UMons) 19 Itérateur ● Exemple – Exemple : utilisation « manuelle » d'un itérateur. Collection<Carre> carres= ...; Iterator<Carre> iter= carres.iterator(); while (iter.hasNext()) { Carre c= iter.next(); /* Fait qquechose avec le carré */ } (c) 2010, Bruno Quoitin (UMons) 20 Itérateur ● Boucle for améliorée – Depuis la version 5.0 de Java, le langage fournit une structure de boucle for supplémentaire appelée la boucle for-each ou boucle for améliorée (enhanced for). – L'objectif de cette boucle est de parcourir séquentiellement l'ensemble des éléments d'un tableau ou d'une Collection. Elle permet de rendre le code plus compact et plus lisible. – La syntaxe de la boucle for améliorée est la suivante for ( nomType nomVariable : tableau/collection ) blocInstructions – La boucle for améliorée ne peut être utilisée que sur une instance qui supporte l'interface Iterable. (c) 2010, Bruno Quoitin (UMons) 21 Itérateur ● Boucle for améliorée – Exemple Collection<Carre> carres= ...; for (Carre c : carres) { /* Fait qquechose avec le carré */ } – Note : il s'agit d'un simple « sucre syntaxique » (une simplification syntaxique). En effet, le code suivant est traduit par le compilateur de façon à utiliser un itérateur, de la même façon que le code montré ci-dessous. Collection<Carre> carres= ...; Iterator<Carre> iter= carres.iterator(); while (iter.hasNext()) { Carre c= iter.next(); /* Fait qquechose avec le carré */ } (c) 2010, Bruno Quoitin (UMons) 22 Itérateur ● Autres itérateurs – Certaines collections peuvent fournir des itérateurs plus spécifiques. Par exemple, ListIterator fourni par LinkedList permet d'avancer mais également de reculer lors du parcours d'une liste chaînée. public interface ListIterator<E> { void add(E e); boolean hasNext(); boolean hasPrevious(); E next(); E previous(); void remove(); void set(E e); } – Dans le passé, l'API Java a également utilisé un itérateur appelé Enumeration qui fournissait des méthodes hasMoreElements() et nextElement(). (c) 2010, Bruno Quoitin (UMons) 23 Itérateur ● Implémenter un itérateur – Soit la classe MyLinkedList qui implémente l'ADT Liste avec une liste chaînée. Comment implémenter un itérateur pour MyLinkedList ? – Résumé de la classe MyLinkedList public class MyLinkedList<T> implements Iterable<T> { private MyNode head; Référence vers la tête de liste. public void add(T o) { ... } public void remove(T o) { ... } public Iterator<T> iterator() { ... } ... private class MyNode { public T data; public MyNode next; } } (c) 2010, Bruno Quoitin (UMons) Classe interne qui représente un noeud de la liste chaînée. 24 Itérateur ● Implémentation – La classe MyLinkedList fournit sa propre implémentation de l'interface itérateur comme classe privée. private class MyIteratorImpl implements Iterator<T> { private MyNode next; public MyIteratorImpl(MyLinkedList l) { this.next= l.head; } Cette variable d'instance représente l'état actuel de l'itérateur (sa position dans la liste). public boolean hasNext() { return (next != null); } } public T next() { T o= next.data; next= next.next; return o; } (c) 2010, Bruno Quoitin (UMons) 25 Itérateur ● Implémentation – Une instance de MyLinkedList peut retourner une instance de la classe itérateur interne et privée en utilisant le design pattern factory. public class MyLinkedList<T> implements Iterable<T> { ... public Iterator<T> iterator() { return new MyIteratorImpl(this); } ... } MyLinkedList<String> l= new MyLinkedList<String>(); ... for (String s: l) System.out.println(s); (c) 2010, Bruno Quoitin (UMons) 26 Visiteur ● Parcours avec visiteur – Autre moyen de parcourir une structure de données sans en révéler l'implémentation : le design pattern visiteur (visitor). – Dans l'itérateur, c'est le client qui récupère chaque élément séquentiellement (avec hasNext() et next()). Avec le visiteur, le client fournit une instance de classe qui est invoquée pour chaque élément par la structure de données. public interface MyVisitor { public void visit(Object o); } public void walk(MyVisitor v) { MyNode temp= head; while (temp != null) { v.visit(temp.data); temp= temp.next; } } (c) 2010, Bruno Quoitin (UMons) 27 Visiteur ● Parcours avec visiteur – Exemple d'utilisation avec une classe visiteur anonyme. MyLinkedList l= new MyLinkedList(); l.add("Titi"); l.add("Tutu"); l.add("Toto"); l.walk(new MyVisitor() { public void visit(Object o) { System.out.println(o); } }); (c) 2010, Bruno Quoitin (UMons) 28 Table des Matières 1. Introduction 1. Collection 2. Map 2. Itérateur 3. Conversion de / vers tableau 4. Table de hachage (c) 2010, Bruno Quoitin (UMons) 30 Java Collections Framework ● Conversion vers un tableau – L'interface Collection définit des méthodes permettant la récupération des éléments d'une collection sous forme d'un tableau. – La méthode de base permettant cette conversion est Object [] toArray() – Exemple Collection<Carre> carres= ...; Object [] tableauCarres= carres.toArray(); – Problème : il n'est pas possible d'effectuer la conversion suivante (le compilateur accepte, mais la VM génère une ClassCastException). Collection<Carre> carres= ...; Carre [] tableauCarres= (Carre []) carres.toArray(); (c) 2010, Bruno Quoitin (UMons) 31 Java Collections Framework ● Conversion vers un tableau – Le JCF fournit une méthode alternative pour convertir une collection vers un tableau correctement typé <T> T [] toArray(T [] array) – Cette méthode utilise le tableau passé en argument pour déterminer le type du tableau retourné. ● ● – Si le tableau passé en argument a une taille suffisante, les éléments de la collection sont copiés dans ce tableau. Sinon, un nouveau tableau est automatiquement alloué. Note : il existe encore d'autres alternatives comme Arrays.copyOf(...) (c) 2010, Bruno Quoitin (UMons) 32 Java Collections Framework ● Conversion vers un tableau – Exemple Collection<Carre> carres= ...; Carre [] tableauCarres= carres.toArray(new Carre [0]); Deux allocations de tableaux sont nécessaires : celui passé en argument et celui créé par toArray. – Exemple Collection<Carre> carres= ...; Carre [] tableauCarres= carres.toArray(new Carre[carres.size()]); Une seule allocation est effectuée. Attention, le tableau retourné par toArray est égal à celui passé en argument. (c) 2010, Bruno Quoitin (UMons) 33 Java Collections Framework ● Conversion à partir d'un tableau – – La conversion d'un tableau vers une instance de Collection est également possible. La méthode de classe asList de la classe utilitaire Arrays permet de créer une instance de List sur base d'un tableau ● static List asList(Object [] a) ● static <T> List<T> asList(T ... a) depuis Java 1.5 Exemple Carre [] tableauCarres= ...; Collection carres= Arrays.asList(tableauCarres); Carre [] tableauCarres= ...; Collection<Carre> carres= Arrays.asList(tableauCarres); (c) 2010, Bruno Quoitin (UMons) 34 Java Collections Framework ● Conversion à partir d'un tableau – La plupart des implémentations de Collection fournissent un constructeur permettant de créer une instance de la collection à partir d'une autre instance. – Par exemple, TreeSet offre le constructeur ● – TreeSet(Collection c) Exemple Carre [] tableauCarres= ...; Collection<Carre> carres= new TreeSet(Arrays.asList(tableauCarres)); (c) 2010, Bruno Quoitin (UMons) 35 Table des Matières 1. Introduction 1. Collection 2. Map 2. Itérateur 3. Conversion de / vers tableau 4. Table de hachage (c) 2010, Bruno Quoitin (UMons) 36 Table de hachage ● Introduction – Une table de hachage(1) est une implémentation de l'ADT association (map). Pour rappel, une association maintient des paires clés / valeurs (k, v). – Une table de hachage utilise typiquement ● ● un tableau de N cellules pour stocker les associations. une fonction de hachage pour déterminer dans quelle cellule du tableau une association doit être stockée. (1) note : Les tables de hachage seront discutées en détails en BAC2 (« Structures de Données »). Cependant, il est déjà utile d'en connaître le principe de fonctionnement. (c) 2010, Bruno Quoitin (UMons) 37 Table de hachage ● Fonction de hachage – Une fonction de hachage prend une clé k (de type Object) en argument et retourne un entier h(k). ● – h : Object → int : k → h(k) L'index dans le tableau d'une paire (k, v) est typiquement obtenue par h(k) mod N paire clé-valeur (k, v) index=h(k) mod N – Tableau v N cellules Cette organisation permet d'effectuer des accès en temps constant : O(1) (c) 2010, Bruno Quoitin (UMons) 38 Table de hachage ● Collisions – La fonction de hachage n'est généralement pas injective. – Par conséquent, il est possible que deux clés k1 et k2 donnent la même position dans le tableau, i.e. h(k1) mod N = h(k2) mod N. – Cette situation est appelée collision. (k1, v1) index=h(k1) mod N (k2, v2) index=h(k2) mod N (c) 2010, Bruno Quoitin (UMons) Tableau ??? N cellules 39 Table de hachage ● Gestion des collisions – Une table de hachage gère les collisions en stockant les associations dont la clé donne la même position dans une liste de collisions (bucket). (k1, v1) Tableau index=h(k1) mod N (k2, v2) index=h(k2) mod N – Liste chaînée (k1, v1) (k2, v2) La performance de l'accès à un élément de la table de hachage peut alors dépendre de la longueur de la liste de collisions, et par conséquent O(M) dans le pire des cas, où M est le nombre d'éléments dans la table de hachage. (c) 2010, Bruno Quoitin (UMons) 40 Table de hachage ● Fonction de hachage en Java – En Java, une fonction de hachage est associée à chaque instance. Cette fonction est définie par la classe Object sous la forme de la méthode ● int hashCode() – Par défaut, la méthode hashCode() retourne l'adresse en mémoire de l'object. – Il est parfois nécessaire de surcharger la méthode hashCode() ● ● Par exemple, la classe String, surcharge hashCode() de façon que deux chaînes de même contenu mais situées à des adresses différentes donnent la même valeur de hachage. La valeur retournée par hashCode() est basée sur le contenu de la chaîne. La fonction de hachage utilisée est la suivante s.length−1 h(s)= ∑ ( 31i i=0 ● (c) 2010, Bruno Quoitin (UMons) × s.charAt (i)) 41 Table de hachage ● Table de hachage – L'exemple suivant illustre l'utilisation d'une table de hachage sous la forme d'une instance de la classe HashMap. Map<String,Integer> resultatsExamen= new HashMap<String,Integer(); resultatsExamen.put("Bruno", 18); resultatsExamen.put("Véronique", 16); resultatsExamen.put("Jef", 12); resultatsExamen.put("Hadrien", 17); resultatsExamen.put("Tom", 17); String key= "Tom"; System.out.println("Résultat de \""+key+"\" : "+ resultatsExamen.get(key)); – Rappel: une clé est associée à une valeur unique ! ● utiliser 2 fois put() avec la même clé effectue un remplacement (c) 2010, Bruno Quoitin (UMons) 42 Table de hachage ● Table de hachage – L'exemple suivant illustre la récupération de l'ensemble des clés et de l'ensemble des valeurs d'une association avec les méthodes keySet() et values(). Map<String,Integer> resultatsExamen= new HashMap<String,Integer(); /* ... */ Set<String> keys= resultatsExamen.keySet(); for (String s: keys) System.out.println("Clé=\""+s+"\""); Collection<Integer> values= resultatsExamen.values(); for (Integer i: values) System.out.println("Valeur=\""+i+"\""); (c) 2010, Bruno Quoitin (UMons) 43