Université de Lorraine - UHP - ESSTIN                   Département d’informatique                               

2A – Informatique  S4                                              JAV_COLL  / H. Nguyen-Phu     

Cr.: 22/08/2002                                                       Dernière MAJ:  2011.04.02  08:11

 

 

   C1.1: LES CLASSES ET INTERFACES DE COLLECTIONS DE L’API

              

Plan:

----

      Hiérarchie des classes

      Les objets de type 'ArrayList' et 'Vector' (T.G.V. à capacité finie)

      Les objets de type ' Stack ' ( pile )

      Les objets de type ' LinkedList ' (LCAB ou Liste Chaînée Auto-référencée et Bilatère)

      Les interfaces ' Iterator ' ('Enumeration') et 'ListIterator'

      Les objets de type ' Properties '  (dictionnaire  à clés uniques)

      Les objets de type ' HashMap/TreeMap ' (table à clés uniques / triées)

      Les objets de type ‘ArrayDeque<E>’ et l’interface ‘Deque<E>’

     

------------------------------------------------------------------------------

 

1. HIERARCHIE DES CLASSES

*************************

 

   Une collection est, par définition, un objet capable de contenir d'autres objets. Les principales collections développées sont:

 

- un 'ArrayList' ou collection dynamique de taille variable,

- un 'Vector' ou tableau dynamique de taille variable,

- une pile implémentée par tableaux  ('Stack') qui est une sous-classe de 'Vector',

- une LCAB ou liste doublement chaînée par référence ('LinkedList' ici) qui est une sous-classe de 'AbstractSequentialList',

- un objet 'Properties' ou collection dynamique à indexation par chaîne,

- la table de hachage ('HashTable') ou collection dynamique à indexation par objet quelconque [version JDK 1.1],

- la table associative  par hachage ('HashMap'), une sous-classe de 'AbstractMap' selon le diagramme des hiérarchies ci-après [à partir de JDK 1.2]. Il est donc recommandé de l'employer à la place de 'Hashtable'.

 

Il en est de même pour 'Iterator' à la place de 'Enumeration', et pour 'ArrayList' à la place de 'Vector'. Toutes ces classes et interfaces font partie du paquet « java.util ».

 

 

Classes:                                        Interfaces:

========                                        ===========


[AbstractCollection] ...........................Collection

--------------------                            ----------

     |_ [AbstractList].......................... |__ List

     |    |_ [AbstractSequentialList]  ........... |__ Set

     |    |     |__ LinkedList         : ......... |__ SortedSet

     |    |_ ArrayList                 : :

     |    |_ Vector                    : :

     |          |__ Stack              : :

     |_ [AbstractSet]..................: :

          |_ HashSet                     :

          |_ TreeSet ....................:

 

[Dictionary]

-----------                                    

     |_ Hashtable ...............................Map 

[AbstractMap] ................................:  ----

-------------                                 .....|__ SortedMap

     |_ HashMap                               :

     |_ TreeMap ..............................:

 

Arrays                                           Map.Entry

                                                

Collections                                      Iterator                                       

                                                 --------

BitSet                                            |_ ListIterator

 

                                                 Comparator

 

StringTokenizer ................................ Enumeration

 

 

La classe abstraite 'AbstractCollection' est la seule classe qui implémente l'interface 'Collection' offrant ainsi l'accès aux principales méthodes:

 

add(Object)           Ajout d'un objet à la collection référencée par 'this'

addAll(Collection)    Ajout des objets d'une collection passée en argument

remove(Object)        Supprime un objet de la collection référencée par 'this'

removeAll(Collection) Enlève tous les objets d'une collection donnée en entrée

contains(Object)      Teste si 'Object' appartient à la collection courante

containsAll(Collection) Idem. mais pour tous les objets d'une collection donnée

size()                retourne le nombre d'objets (taille) de la collection

iterator()            retourne un itérateur (cf. 'java.util.Iterator')

toArray()             convertit la collection courante en tableau

 

 

L'interface 'java.util.List' qui dérive de l'interface 'Collection' autorise deux objets (ou plus) identiques au sein d'une même collection. Elle est implémentée par les classes 'ArrayList' et 'Vector'.

 

L'interface 'java.util.Set' qui dérive aussi de l'interface 'Collection' n'autorise pas d'objets identiques au sein d'une même collection. Elle est implémentée par les classes 'HashSet' (ensemble d'objets non ordonnés) et 'TreeSet' (ensemble d'objets ordonnés).

 

 

2.- Les objets de type 'ArrayList' et 'Vector'

**********************************************

Ces classes ont pratiquement les mêmes fonctionnalités. L'une des principales différences réside dans le fait qu'aucune des méthodes de la classe 'ArrayList' n'est synchronisée. Les constructeurs et les principales méthodes sont:

 

 Classe 'ArrayList'  Classe 'Vector'              Remarques         

 -----------------   --------------               ---------      

 ArrayList()         Vector()                    Constructeur sans arguments

                     Vector(int capacity)        Constructeur avec capacité déf.

                     Vector(int capa, int dCap)  Constructeur avec capacité init.

                                                 'capa' puis par incrément dCap

 

 add(Object)         addElement(Object)          Ajout en queue d'un objet

 add(int, Object)    insertElementAt(Object,int) Ajout mais à l'indice 'int'

 

                     capacity()                  renvoie la capacité

                     clear()                     efface tous les éléments

                     clone()                     renvoie un clone du 'Vector'

 

 contains(Object)    contains(Object)            Test d'appartenance

 get(int pos)        elementAt(int pos)          renvoie l'objet à l'indice 'pos'

                     elements()                  renvoie la liste des élements

 indexOf(Object)                                 renvoie l'indice de 'Object'

 

                     isEmpty()                 teste si l'objet 'Vector' est vide

 iterator()                                    renvoie un itérateur

                     lastElement()             renvoie le dernier élément

 

                     removeElement(Object)     retire l'élément 'Object'

 remove(int i)       removeElementAt(int i)    retire l'élément à l'indice 'i'

                                               en décalant en amont les éléments

                                               qui suivent

                     removeAllElements()       retire tous les éléments de 'Vector'

 

 set(int i, Object)  setElementAt(Object, int i) modifie l'élément en position 'i'

                     setSize(int)              modifie la taille d'un 'Vector'

 

 size()              size()                    retourne la taille courante

                     toString()                renvoie une chaîne contenant la

                                               liste des éléments d'un 'Vector'

                     trimToSize()              pour forcer: capacité == taille

 

 

Voici un exemple typique d'emploi des objets correspondants:

 

import static java.lang.System.*;  // cf. Java 5

import java.util.*;

import java.io.*;

public class vecarray    {

   ArrayList<String> shape = new ArrayList<String>();  // cf. Java 5

   Vector<String> color = new Vector<String>(10,5);

   public static void main(String [] arg)  {

     vecarray  O = new vecarray();  // O : un objet courant

     out.println("\nVECARRAY :  Un exemple d'emploi de 'ArrayList', 'Vector',");

     out.println("\t'Iterator' et 'Enumeration' (c)~/2a env. MAJ: 2006.08.23 19h00\n");

     O.shape.add( (new String( "rectangle")  )  );

     O.shape.add("triangle");

     O.shape.add("cercle");

     for (int i=0; i< O.shape.size(); ++i)

            out.println(O.shape.get(i));

 

/*   // Que se passe-t-il si on dé-commente ce bloc ?

     Iterator it = O.shape.iterator();

     while(it.hasNext()) {

        String str = (String) it.next();

        out.println(str);

     }

*/

     out.printf("\n");   // cf. Java 5

     O.color.add("bleu  "); O.color.add("blanc ");

     O.color.addElement("rouge ");

     for (int i=0; i < O.color.size(); ++i)

          out.println(O.color.get(i));

 

/*   // Que se passe-t-il si on dé-commente ce bloc ?

     Enumeration listColor = O.color.elements();

     while(listColor.hasMoreElements())  {

        String str = (String) listColor.nextElement();

        out.println(str);

     }

*/

     if ( O.color.elementAt(O.color.size()-1).equals( O.color.lastElement()))

          out.println("\nDernier élément = "+ O.color.lastElement());

     out.println("Capacité= "+O.color.capacity()+" Taille= "+O.color.size());

     O.color.trimToSize();

     out.println("Nouvelle capacité = "+O.color.capacity() );

     es.attente();

  }

}

Question: Qu'affiche le programme Java ci-dessus (cf. vecarray.java)?

--------

 

 

3.- Les objets de type 'Stack'

******************************

  La structure de données "pile" qui est une liste linéaire de type 'LIFO' ('Last In First Out') est implantée en Java via la classe 'java.util.Stack'. Comme elle hérite de la super classe 'java.util.Vector', elle reprend toutes ses méthodes mais l'enrichit de cinq fonctionnalités à savoir:

 

 - push()   pour empiler un élément en sommet de la pile courante,

 - pop()    pour dépiler un élément du sommet de la pile,

 - peek()   pour consulter l'élément situé au sommet SANS dépilement,

 - empty()  pour savoir si la pile courante est vide,

 - search() recherche un élément au sein de la pile en indiquant sa position.

 

Exercice 1: Ecrire un programme Java pour vérifier si une expression arithmétique est bien parenthésée i.e. le nombre de parenthèses ouvrantes est égal à celui des parenthèses fermantes et dans le bon sens.

 

Solution 1: (cf. parenthe.java)

 

import static java.lang.System.*;  // cf. Java 5

import java.util.*;

import java.io.*;

 

public class parenthe  {

 

   private String  expr;    

   private Stack<Character> pileParentheses;

  

   private parenthe(String uneExpression)  { 

      expr = uneExpression;

      pileParentheses = new Stack<Character>();  // cf. Java 5

   }

  

   private boolean valide()    {

      try   {

         for(int i = 0; i<expr.length(); i++)

           switch(expr.charAt(i)) {

             case '(':

                pileParentheses.push(new Character('('));  break;

             case ')':

                pileParentheses.pop();  break;

            }

      } catch (EmptyStackException  ese) {  return false; }     

 

      return  pileParentheses.empty();

   }

 

   public static void main(String [] arg)   {

      out.println("\nparenthe : Un exemple d'emploi de 'Stack' en Java 5");

      out.println("\t(c) ~/2a env. 2006.08.23  19h35 \n");

      out.println("Tapez votre expression \u00e0 valider en parenth\u00e9sage");

      out.println("Exemple:  (x+y)  ou  ((1-3)/2))  ou  1+3)*4(  etc ... \n");

      parenthe O = new parenthe(es.LireCh());   // O : une instance courante

      out.println("\nVotre parenth\u00e9sage est "+(O.valide()?"bon!":"incorrect!"));

      es.attente();

   }

}

 

 

 

4.- Les objets de type 'LinkedList'

***********************************

    La classe 'java.util.LinkedList' définit aussi un type abstrait de données (TAD) nommé LCAB « Liste Chaînée par Auto-référence et Bilatère" (ou encore liste doublement chaînée via les deux pointeurs ‘suivant’ et ‘prédécesseur’). Ainsi on peut parcourir en allant du premier au dernier élément et aussi dans le sens inverse.

 

Chaque objet référencé est du type générique 'E' (à compter de JDK 1.5): on peut donc y insérer des objets de type quelconque. On retrouve des méthodes analogues à celles de 'Vector' mais aussi des méthodes  telles que:

 

 addFirst()         getFirst()        removeFirst()    peek()     listIterator()

 addLast()          getLast()         removeLast()     poll()     offer()

 

      Méthodes “communes”’ des classes:

 Classe 'LinkedList'  Classe 'Vector'            Remarques         

 ------------------   --------------             ---------      

 LinkedList()         Vector()                   Constructeur sans arguments

 ...                  ...                        Autres constructeurs ...

 

             boolean add(E o)                    Ajout en queue d'un élément E

           void add(int i, E o)                  Ajout mais à l'indice 'i'

 

           boolean addAll(coll)                  Ajout d'une collection 'coll'

 

 void addFirst(E o)   void add(0, E o)           Adjonction en tête ("Adjt")

 void addLast(E o)    void addElement(E o)       Adjonction en queue ("Adjq")

 

             void clear()                        RAZ de l’instance courante

            Object clone()                       renvoie un clone de ‘this’

 

          boolean contains(Object e)             Test d'appartenance de ‘e’

 

 E get(int i)         E elementAt(int i)         renvoie l'objet à l'indice 'i'

 E getFirst()         E firstElement()           renvoie l'élément de tête

 E getLast()          E lastElement()            renvoie le dernier élément

 

             int hashCode()                      renvoie le code de hachage

          int indexOf(Object e)                  renvoie l'indice de 'e'

         int lastIndexOf(Object e)               renvoie le dernier indice de 'e'

 

 boolean isEmpty()    boolean isEmpty()          teste si l'objet courant est vide

 

 ListIterator<E> listIterator(int i)  - NEANT -  renvoie une liste d’itérateurs ...

 boolean offer(E o)                              ajout en queue de ‘o’    

 E peek()             - NEANT -                  renvoie l'élément de tête uniquement

 E poll()             - NEANT -                  renvoie et supprime la tête de liste

                 

           E remove(int i)                       supprime et renvoie l'élément en ‘i’

        boolean remove(Object o)                 supprime l'objet 'o' de « this »

 

 E remove()           - NEANT -                  supprime et renvoie l'élément de tête

 E removeFirst()      <=> remove(0);             supprime et renvoie la tête de liste 

 E removeLast()       <=> remove(size()-1);      supprime et renvoie le dernier élément

 

                      boolean removeAll(Collection<?> c) supprime ‘c’ du vecteur courant
                      void removeAllElements()   RAZ le vecteur courant

                      boolean removeElement(Object o) supprime ‘o’ du vecteur courant
                      void removeElementAt(int i) supprime l'élément en ‘i’ du vecteur courant

 

          E set(int i, E elt)    remplace l’élément en ‘i’ par ‘elt’ et retourne l’ancien       

               int size()                        retourne la taille courante de « this »

 

 

 

5.- Les interfaces 'Iterator' ('Enumeration') et 'ListIterator'

***************************************************************

  Java propose d'employer à partir de sa version JDK 1.2 l'interface 'Iterator' pour rechercher un élément au sein d'un objet de la famille des collections. Une interface est une classe "implémentable" par d'autres classes via le

mot-clé 'implements'. Le code de chaque méthode est fourni pour un emploi spécifique et associé à une implémentaion particulière. On distingue:

 

- hasNext() qui renvoie Vrai si l'itérateur courant indique qu'il y a encore au moins un élément de la collection ('Vector', 'LinkedList'...) à étudier;

- next() qui permet à l'itérateur de référencer (pointer sur) l'élément suivant;

- remove()  supprime de la collection l'élément qui vient d'être référencé.

 

Bien que le code soit spécifique à chacune des collections, leur emploi reste le même, ceci quelque soit le type de collections.

 

L'interface 'java.util.Enumeration' possède des propriétés similaires à l'interface 'java.util.Iterator'. Depuis la version 1.2, seule la classe  StringTokenizer  implémente encore cette interface. Une comparaison succinte entre les deux interfaces est donnée par:

 

  Interface 'Iterator'             Interface 'Enumeration'

  -------------------              -----------------------

  hasNext()                        hasMoreElements()

  next()                           nextElement()

 

Question 2: Utiliser 'Iterator' pour remplacer, dans 'vecarray.java', les lignes:

---------- 

     for (i=0; i< O.shape.size(); ++i)   // Rappel:

          out.println(O.shape.get(i));  // 'shape' est une instance de 'ArrayList'

Réponse 2:

---------

     Iterator it = O.shape.iterator();

     while(it.hasNext())    {

        String str = (String) it.next();

        out.println(str);  }

 

Question 3: Utiliser 'Enumeration' pour remplacer les deux lignes:

---------- 

     for (i=0; i < O.color.size(); ++i)     // Rappel:

          out.println(O.color.get(i));     // 'color' est une instance de 'Vector'

Réponse 3:

---------

     Enumeration listColor = O.color.elements();

     while(listColor.hasMoreElements()) {

        String str = (String) listColor.nextElement();

        out.println(str);              }

 

 

Par ailleurs, l'interface 'java.util.ListIterator' qui dérive de l'interface 'Iterator' permet de parcourir la collection dans les deux sens et aussi de modifier ou d'ajouter un élément de la collection:

 

 Interface 'ListIterator'

 ------------------------

 hasPrevious()   teste s'il existe un élément précédent dans la collection

 set(Object o)   modifie l'élément courant par le nouvel objet 'o'

 add(Object o)   ajoute l'objet 'o' dans la collection ...

 nextIndex()     renvoie l'indice de l'élément suivant indiqué par l'itérateur

 previousIndex() renvoie l'indice de l'élément précédent indiqué par l'itérateur

 

 

Exercice 2: Ecrire un programme Java pour implémenter le type abstrait de donnée "FADE" ou file à double entrée (file avec deux entrées mais une seule sortie) en employant la classe 'LinkedList' et l'interface 'ListIterator'. Simuler une file d'attente de personnes valides qui laissent, par courtoisie, la priorité aux personnes à mobilité réduite bien que ces personnes arrivent après [A terminer en TPP ...].

 

 

 

6.- Les objets de type 'Properties'

***********************************

   La classe 'java.util.Properties' permet d'étudier des collections d'objets sous forme "clef - valeur" où la clef et la valeur sont des chaînes 'String'. C'est déjà une structure de données de type "dictionnaire" où la clé, appelée aussi l'index, offre un moyen plus efficace  que les indices dans les tableaux dynamiques 'Vector' pour retrouver la valeur associée.

 

   En général, on importe cette classe pour gérer les variables d'environnement système. Dans  l'exemple ci-dessous, la méthode 'getProperties()' de la classe 'java.lang.System' permet de récupérer ces variables et la méthode 'list(System.out)' de la classe 'java.util.Properties' affiche la liste sur la sortie standard.

 

 import static java.lang.System.*;  // cf. Java 5

 import java.util.Properties; // cf. list_sys.java

 public class list_sys  {

   public static void main(String [] arg)   {

      out.println("list_sys: Affichage des variables d'env. de Java - 2005.10.01");

      getProperties().list(out);

      es.attente();

   }

 }

 

Les principales méthodes de la classe 'java.util.Properties' sont:

- load(f_name) pour charger de la table des propriétés depuis un fic. 'f_name',

- store(f_name) pour enregistrer la table associative dans un fic. 'f_name',

- setProperty(clef,valeur) pour enrichir la table par une nouvelle propriété.

 

Exercice 3: Ecrire un programme pour gérer une table associative des romans avec chargement à partir d'un fichier disque, puis enrichissement par de nouveaux enregistrements en cours d'exécution et enfin, avec sauvegarde sur disque sous le même nom.

 

Solution partielle: (cf. l_roman.java)

------------------ 

 import static java.lang.System.*;  // cf. Java 5

 import java.util.*;

 import java.io.*;

 class l_roman   {

    Properties  lesRomans;

    l_roman()  {  lesRomans = new Properties();  }

    void chargerRomans(String f_n)   {  // f_n: file name

        try  {

           BufferedInputStream jin= new BufferedInputStream(new FileInputStream(f_n));

           lesRomans.load(jin);

           jin.close();

     } catch(IOException  ioe) { err.println("Pas de chargement (fic. nouveau) ..."); }

   }

   void ajout_Romans()  {

     String auteur, oeuvre;

     boolean conti = true;

     do {

       try {

         auteur = es.LireCh("Tapez le nom de l'auteur (ou retour chariot pour quitter): ");

         oeuvre = es.LireCh("Tapez le titre de l'oeuvre (ou retour chariot pour quitter): ");           

         if (! auteur.equalsIgnoreCase("") && !  oeuvre.equalsIgnoreCase("") )

            lesRomans.setProperty(auteur, oeuvre);            

       else  conti = false;

          } catch(Exception  e) { err.println("Pb. de saisie des données !");  }

      } while(conti);

   }

   void afficherRomans()  { 

      out.println("Contenu de la table 'Auteur=Oeuvre' :");

      lesRomans.list(out);

   }

   void sauvegarderRomans(String f_n)   { // f_n: file name

      try   {

          BufferedOutputStream jout= new BufferedOutputStream(new FileOutputStream(f_n));

          lesRomans.store(jout,"Liste des romans MAJ le ...");

          jout.close();

          out.println("Sauvegarde de la liste des romans terminée !");

       } catch(IOException er) { err.println("Pb. de sauvegarde (fic. protégé !) ..."); }

     }

   public static void main(String [] arg)  {

     String nom_fic;

     l_roman table = new l_roman();

     out.println("l_roman : Emploi de 'java.util.Properties' (c)~/2A env. 2002.08.29 22H21");

     if (arg.length == 0)    nom_fic = "l_roman.txt";

     else                    nom_fic = arg[0];

     table.chargerRomans(nom_fic);

     table.ajout_Romans();

     table.afficherRomans();

     table.sauvegarderRomans(nom_fic);

     es.attente();

   }

}

// Question: Que se passe-t-il si on souhaite enregistrer:

 

 Hugo :      Les Misérables

 Steinbeck : Les Raisins de la colère

 Duras :     Un barrage contre le Pacifique

 Duras :     L'Amant

 Zola :      Germinal

 

Réponse: On ne peut enregistrer ici qu'un seul ouvrage de M. Duras car deux clés ne peuvent être égales. On peut résoudre ce problème par l'emploi des tables associatives généralisées de couples <Object, Object>.

 

 

7.- Les objets de type 'HashMap' [ou 'Hashtable']

********************************

   Pour cela, on va construire une table de hachage, objet de la classe 'java.util.HashMap' nommé "hash" et formé de couples <auteur, valeur>. Ici, chaque clé 'auteur' correspond à un nom d'auteur et chaque 'valeur' est au fait un tableau vectoriel 'Vector' contenant éventuellement plusieurs titres du même auteur. Ainsi, la contrainte "deux clés ne peuvent être égales" est respectée comme le montre l'exemple ci-dessous (cf.  tab_hach.java) :

 

import static java.lang.System.*;  // cf. Java 5

import java.io.*;

import java.util.*;

 

/**

  * Classe 'tab_hachage' :  Test de qq. méthodes du conteneur 'HashMap'

  * @author   (c) ~/2A env.

  * @version  1.1   2006.08.23

  * @since    1.0   2005.10.01

  */

class tab_hachage   {

 

   private    HashMap<String, Vector<String>>    hash;  // cf. Java 5

 

   public tab_hachage() { hash= new HashMap<String, Vector<String>>(); }

 

   public void insere(String author, String novel)  {

      Vector<String> livres = hash.get(author);

      if    (livres == null)   {

         // l'auteur 'author' est nouveau ici <==> il n'est pas enregistré encore !

          Vector<String> v = new Vector<String>();

          hash.put(author, v);

          v.add(novel);

      }  else  {

        // le nom de l'auteur est déjà enregistré dans la table !

          Vector<String> titres = livres;

          if ( !titres.contains(novel) )  titres.add(novel);

          // on ajoute le nouveau roman 'novel' parmi les titres anciens

       }

   }

 

   public void affich()  {

       Iterator it = hash.keySet().iterator();

       while(it.hasNext())  {

           String str = (String) it.next();

           out.println(str+" ==> ");

           Vector<String> titres = (Vector<String>) hash.get(str);

           for (int i=0; i<titres.size(); ++i)   {

                   String s = (String) titres.elementAt(i);

                   out.println("                       "+s); 

           }

       }

    }

}

 

/**

  * Classe 'tab_hach' :  pour tester la classe 'tab_hachage'

  * @author   (c) ~/2A env.

  * @version  1.1   2006.08.23

  * @since    1.0   2005.10.01

  * @see      tab_hachage

  */

 

public class tab_hach   {

   public static void main (String [] arg)  { 

       out.println("\ntab_hach : Un exemple d'emploi de HashMap en Java 5");

       out.println("\t(c)~/2a env. 2006.08.23  21h11 \n");

       tab_hachage th = new tab_hachage();

       th.insere("Steinbeck","Les Raisins de la col\u00e8re");

       th.insere("Hugo","Les Mis\u00e9rables");

       th.insere("Duras","Un barrage contre le Pacifique");

       th.insere("Duras","L'Amant");

       th.insere("Zola","Germinal");    

       th.insere("Zola","La B\u00eate humaine");

       th.affich();

       es.attente();

  }

}

 

Exercice 4 [A FAIRE] :

1-    Une exécution de ce programme donne le résultat typique suivant [A compléter...]

2-    Remplacer le conteneur ‘Vector’ par ‘ArrayList’ désormais et montrer qu’on aboutit aux mêmes résultats.

 

 

 

Question: Comment obtenir une table triée selon l'ordre lexicographique des clés ?

 

Réponse:  Il suffit de remplacer 'HashMap' par 'TreeMap' (cf. tab_hatr.java')

 

import static java.lang.System.*;  // cf. Java 5

import java.io.*;

import java.util.*;

/**

  * Classe 'tab_hachage_triee' :  Test de qq. méthodes du conteneur 'TreeMap'

  * @author   (c) ~/2A env.

  * @version  1.15  2006.08.24

  * @since    1.0   2005.10.01

  */

class tab_hachage_triee   {

 

   private  TreeMap<String, ArrayList<String>>  hash;   // Java 5

   public tab_hachage_triee()  {

      hash= new TreeMap<String, ArrayList<String>>(); }

 

   public void insere(String author, String novel)   {

      ArrayList<String> ouvrages = hash.get(author);

      if    (ouvrages == null)    {

     // l'auteur 'author' est nouveau ici <==> il n'est pas enregistré encore !

        ArrayList<String> v = new ArrayList<String>();

        hash.put(author, v);

        v.add(novel);

      }

      else  { // le nom de l'auteur est déjà enregistré dans la table !

         ArrayList<String> titres = ouvrages;

         if ( !titres.contains(novel))  titres.add(novel);

         // on ajoute le nouveau roman 'novel' parmi les titres anciens

      }

   }

   public void affich() {

      Iterator it = hash.keySet().iterator();

      while(it.hasNext())  {

          String str = (String) it.next();

          out.println(str+" : ");

          ArrayList<String> titres = (ArrayList<String>) hash.get(str);

          for (int i=0; i<titres.size(); ++i)  {

              String s = (String) titres.get(i);

              out.println("                       "+s);

          }

       }

    }

}

 

/**

  * Classe 'tab_hatr' :  pour tester la classe 'tab_hachage_triee'

  * @author   (c) ~/2A env.

  * @version  1.15  2006.08.24

  * @since    1.0   2005.10.01

  * @see      tab_hachage_triee

  */

public class tab_hatr    {

    public static void main (String [] arg)  { 

       out.println("\ntab_hatr: Un exemple d'emploi de TreeMap (Java 5) pour trier la");

       out.println("table via un ABO des clés (c)~/2a env. 2006.08.24 09h53\n");

       tab_hachage_triee th = new tab_hachage_triee();

       th.insere("Zola","La B\u00eate humaine"); 

       th.insere("Zola","Germinal");         // insertions

       th.insere("Hugo","Les Mis\u00e9rables");   // dans le désordre ...

       th.insere("Duras","L'Amant");

       th.insere("Steinbeck","Les Raisins de la col\u00e8re");

       th.insere("Duras","Un barrage contre le Pacifique"); 

       th.affich(); 

    // On doit obtenir la liste par ordre alphabétique des auteurs

    // mais les titres des ouvrages restent non triés

       es.attente();

    }

}

 

 

En effet, l'interface 'java.util.Map' est implémentée par les sous-classes 'HashMap' et 'TreeMap' (table dont les clés sont représentées par un arbre binaire ordonné ou ABO – cf. chap. suivant). Elle spécifie une collection de couples <Clé, Valeur> permettant de localiser une et une  seule clé, ceci à partir de JDK 1.2.

 

Le tableau récapitulatif ci-dessous compare les principales méthodes des TAD 'tables associatives' en fonction des différentes versions de Java:

 

HashMap/TreeMap (JDK 1.2) | Hashtable (JDK 1.1) | Fonctionnalités

--------------------------|---------------------|---------------------------

HashMap() / TreeMap()     | Hashtable()         | Constructeurs sans arguments

 

containsKey(Object clef)  | containsKey(Object) | Test d'existence d'une clé

containsValue(Object val) | contains(Object)    | Test d'existence d'une valeur

 

boolean isEmpty()         | boolean isEmpty()   | teste si l'objet Map est vide

 

keySet()                                          renvoie un objet 'Set' de clés

values()                                          renvoie un objet collection de valeurs

                          | keys()                renvoie un objet 'Enumeration' de clés

                          | elements()            renvoie un objet 'Enumeration' de valeurs

                          | rehash()              réorganise les clés pour optimiser l'accès...

 

get(Object clef)          | get(Object clef)    | renvoie un objet Map s'il existe (ou null sinon)

put(Object k, Object v)   | put(Object, Object) | modifie l'objet ayant la clé 'k' par la valeur 'v'

remove(Object k)          | remove(Object k)    | supprime l'objet ayant la clé 'k'

 

clear()                   | clear()             | supprime tous les couples <clé, valeur>

size()                    | size()              | renvoie la taille de l'objet 'Map'

 

Object getKey()                                   retourne une clé via l'interface 'Map.entry'

Object getValue()                                 retourne une valeur via l'interface 'Map.entry'

Object.setValue(Object)                           modifie une valeur via l'interface 'Map.entry'...

entrySet()                                        renvoie un objet 'Set' d'objets 'Entry'

                                                          (couple <clé, objet>)

 

Un exemple d'application de la classe 'TreeMap' est le comptage d'occurrences de mots d'un fichier texte donné.

 

Exercice 5: Analyser le fichier tab_occu.java ci-après :

 

import static java.lang.System.*;  // cf. Java 5

import java.io.*;

import java.util.*;

/**

  * Classe 'tab_occu' :  pour tester la classe 'StreamTokenizer' de l'API

  * @author   (c) ~/2A env.

  * @version  1.1   2006.08.23

  * @since    1.0   2005.10.01

  */

public class tab_occu  {

 

   TreeMap<String, Integer>  tableMots;

   StreamTokenizer inFlux;

 

   tab_occu(String nomFic) throws java.io.FileNotFoundException   {

     tableMots= new TreeMap<String, Integer> ();

     inFlux = new StreamTokenizer(new BufferedReader(new FileReader(nomFic)));

 //  inFlux.ordinaryChar('.');  

   }

 

   public void comptageMots() throws java.io.IOException  {

     Integer tmp, un = new Integer(1);

     while (inFlux.nextToken() != StreamTokenizer.TT_EOF)  { 

          if ( inFlux.ttype == StreamTokenizer.TT_WORD)   {  

             Object dejaVu = tableMots.get(inFlux.sval);

             if( dejaVu == null)    tmp = un;

             else  tmp = new Integer(Integer.parseInt(dejaVu.toString())+1);

             tableMots.put(inFlux.sval, tmp);

          }        

      }

  }

 

  public void affich()   {

     Iterator it = tableMots.keySet().iterator();

     while(it.hasNext())   {

        String str = (String) it.next();

        out.println(str+" : "+ tableMots.get(str) + " occurence(s)" );

     }

  }

 

  public static void main (String [] arg) throws java.io.IOException  { 

       out.println("tab_occu: Table d'occurences de mots via 'StreamTokenizer' en Java 5");

       out.println("\td'après: Leduc & Leduc pp.129-130 (c)~/2a env. 2006.08.23 21h22\n");

       String fName;

       if (arg.length == 0 ) fName = "tab_occu.java";

       else                  fName = arg[0];

       tab_occu toc = new tab_occu(fName);

       toc.comptageMots();

       toc.affich();

       es.attente();

   }

 

}

 

 

Remarque :   Si le fichier d’entrée est   tab_occu.in  ,  on obtient  les résultats  dans  tab_occu.txt .

 

Exercice 6: Etendre la classe 'es' en écrivant une méthode de lecture récursive croisée de valeurs entières signées occupant un octet (cf. type ‘byte’ en Java) via le clavier en gérant l'exception d'E/S.

 

Exercice  7: Etendre la classe 'es' en écrivant une méthode de lecture récursive de valeurs booléennes par le clavier en gérant l'exception d'E/S. On acceptera indifféremment des majuscules / minuscules:  {true, false, Vrai, Faux, VRAI, FAUX, V, F, etc... } [A TERMINER en TPP].

 

 

8. Les objets de type ‘ArrayDeque<E>’ et l’interface ‘Deque<E>’

 

    La classe 'java.util.ArrayDeque<E>' définit une file d’attente avec deux extrêmités  nommée Dèque (i.e. Deque ou « double ended queue » en anglais) implémentée par un tableau de taille variable à partir de la version JDK 1.6.  On peut insérer et supprimer aux deux bouts de la structure de donnée. On retrouve ainsi les caractéristiques à la fois d’une file d’attente émulée par ‘java.util.LinkedList’ et d’une pile implantée par ‘java.util.Stack’. Les principales méthodes de cette classe sont à comparer donc avec celles des classes  'LinkedList<E>' et 'Stack<E>'.

     La classe 'ArrayDeque<E>' implémente l’interface ‘Deque<E>’ qui fournit le profil des méthodes permettant d’adjoindre, d’enlever et de consulter un élément dans la dèque courante. Chacune de ces méthodes existe en deux versions: la première génère une erreur d’exécution appelée exception si l’opération échoue alors que la seconde retourne une valeur spéciale (soit ‘null’ soit ‘false’).

Les douze méthodes décrites précédemment sont résumées ci-après:

 

 

 

Premier Element (‘Head’)

Dernier Element (‘Tail’)

 

‘Throws’ exception

Valeur spéciale

‘Throws’ exception

Valeur spéciale

Adjonction

addFirst(e)

offerFirst(e)

addLast(e)

offerLast(e)

Suppression

removeFirst()

pollFirst()

removeLast()

pollLast()

Consultation

getFirst()

peekFirst()

getLast()

peekLast()

 

L’interface ‘Deque<E>’ spécialise l’interface ‘Queue<E>’. Lorqu’une dèque est employée comme une file, il en résulte un comportement FIFO (« First-In-First-Out »). Des éléments sont insérés à la fin mais enlevés au début de la dèque. Les méthodes héritées de l’interface ‘Queue<E>’ sont équivalentes aux propres méthodes de ‘Deque<E>’ selon la table comparative ci-dessous:

 

Méthodes de Queue<E>

Méthodes équivalentes  de  Deque<E>

add(e)

addLast(e)

offer(e)

offerLast(e)

remove()

removeFirst()

poll()

pollFirst()

element()

getFirst()

peek()

peekFirst()

 

Des dèques peuvent être utilisées comme des piles LIFO («Last-In-First-Out»). Lorsqu’une dèque se comporte comme une pile, des éléments sont insérés et enlevés toujours d’une même extrêmité (soit au début soit à la fin). Les méthodes de la classe ‘Stack<E>’ sont donc équivalentes à celles de ‘Deque<E>’ de deux versions :

 

Méthodes ‘Stack’

Méthodes équivalentes (I)

Méthodes équivalentes (II)

push(e)

addFirst(e)

addLast(e)

pop()

removeFirst()

removeLast()

peek()

peekFirst()

peekLast()

 

 

 

Comme les objets ‘ArrayDeque’ sont des dèques implantées via des tableaux munis de deux index entiers pointant sur la tête (‘head’) et aussi sur le dernier élément (‘tail’), l’emploi des  méthodes (I) est conseillé pour émuler une pile générique (cf.  PileG21.java – Emploi conseillé). On n’est pas obligé de privilégier la version des méthodes équivalentes (II) afin d’éviter des décalages inutiles comme pour le cas des objets de ‘ArrayList’ (cf. PileG22.java – A éviter !) ou de ‘Vector’ (cf. PileG26.java – A éviter !). Pour les instances de type ‘LinkedList<E>’ qui implémente aussi l’interface ‘Deque<E>’ à partir de JDK 1.6, les deux versions précédentes présentent la même complexité temporelle en O(1) (cf. Chap. « Complexité des algorithmes ») mais, dans tous les cas, la durée d’exécution via ‘ArrayDeque’ reste la plus courte (cf. fichiers  PileGjTest,  FileGjTest  et  PileGj &  FileGj.java avec j = {1,…, 6} ).

 

A consulter : Gestion des exceptions en Java.