|
~Les collections en Java

La méthode la plus répandu en programmation pour stocker une grande quantitée de données d'une même nature passe par
l'utilisation, somme toute naturelle, des tableaux. Cependant, dans de nombreuses situations le programmeur peut être amené
à gérer des listes de données dont il ne connait pas par avance la taille. Ou encore des listes de données mixtes. C'est dans ce
genre de situations qu'interviennent les collections d'objets. 

Introduction aux collections
Bien sûr, il est souvent possible de déclarer un tableau dont la taille est suffisament importante pour les besoins du programmes.
Prenons le cas d'un algorithme qui stockera des données dans un tableau. Si l'on sait juste que ces données n'excéderont pas
500 élements, nous pouvons nous contenter de la déclaration suivante:
protected Object[] datas = new Object[500];
Malheureusement, une telle solution n'a que peu d'avantages. Certes, l'accès aux éléments du tableau sera relativement rapide
et facile à programmer. Pourtant, une telle méthode peut entraîner un grave gaspillage des ressources. Si d'aventure votre
tableau ne venait à contenir que 3 éléments, 497 allocations mémoires inutiles auront été effectuées. En outre, votre programme
peut tout à fait par la suite avoir besoin de plus de 500 éléments, vous obligeant à modifier votre code source.
Mais louée soit la POO, les collections sont là ! Un collection est une sorte de tableau possédant deux limites: une de taille (dite
"size") et une de capacité (dite "capacity"). Par exemple, une collection de String peut avoir une capacité de 10 éléments et une
taille de 3 éléments. Vous l'aurez compris, la taille indique le nombre d'objets que contient effectivement la collection et la
capacité le nombre d'objets que la collection est susceptible d'accueillir.
L'intérêt des collections n'est pas encore flagrant... nous y venons. Les collections ont pour particularité très intéressante de se
redimenssionner automatiquement lorsque cela est nécessaire. Imaginons un instant que vous ayiez une collection possédant
une capacité de 10 éléments et une taille de 10 éléments. Si vous ajoutez un élément, alors la capacité de la collection sera
automatiquement augmentée (en Java, les tailles doublent ou se multiplient par 0.75). Cependant, le redimenssionement
automatique ne se fait que lors de l'addition d'un élément. Pour diminuer la capacité d'une collection après un retrait, vous devez
le faire manuellement, en appelant une fonction prévue à cet effet (généralement nommée trimToSize).
La plate-forme Java propose de nombreuses collections toutes plus utiles les unes que les autres (citons Stack, LinkedList, ...).
Du lot, deux se dégagent réellement: Vector et Hashtable. Nous allons les étudier ainsi que leurs deux dérivés:
ArrayList et HashMap.
Ces deux classes font parties du package java.util de Java. Ces deux objets sont la forme la plus classique de collection.
Tout d'abord sachez que ArrayList propose exactement les mêmes fonctions que Vector. Les seules différences
résident dans le nom des fonctions des deux objets. ArrayList colle en effet aux conventions de nomination de Sun, au
contraire de Vector. De plus ArrayList n'est pas thread-safe. C'est à dire que le mot clé synchronized
n'est pas appliqué à ses méthodes. Il en résulte une collection plus rapide à manipuler que Vector mais dangereuse dans
le cadre de l'utilisation de threads.
Constructeurs
Dans la suite de ce cours, nous n'emploierons que l'objet Vector, plus répandu car existant dans le JDK 1.1.
Pour construire une collection de type Vector, il existe deux constructeurs principaux:
import java.util.Vector;
public class CollectionTest
{
protected Vector v = new Vector();
protected Vector v2 = new Vector(200);
}
Le premier constructeur crée une nouvelle collection possédant une capacité de 10 éléments. C'est peu. C'est pour cela que vous
pourrez, et devrez, faire souvent appel au second constructeur. Celui-ci permet de déclarer la capacité initiale. Ici, contrairement
aux tableaux, il ne s'agit plus de deviner la capacité maximale de votre ensemble de données, mais minimale. Ainsi, si vous savez
que vous ajouterez au moins 175 éléments à la collection, donnez lui une capacité de 200. Pourquoi donc ? Tout simplement
parce que lorsque vous atteignez la capacité limite, l'allocation mémoire est doublée mais surtout la collection effectue une recopie
des éléments existants ! Ainsi, si vous avez 175 éléments dans une collection de capacité 175, l'ajout d'un élément provoquera
la création d'un tableau temporaire de 175 éléments, d'un nouveau de 350 et d'une recopie des 175 anciens éléments dans le
nouveau tableau. Un coup dur pour les performances.
Ajouter un élément
L'ajout d'un élément à un Vector se fait de la manière suivante:
Object o = new Object();
vector.addElement(o);
La méthode addElement existe aussi sous le nom de add. Cependant, cette deuxième forme n'existe qu'à partir du
JDK 1.2. Vous noterez que ces méthodes n'accepte en paramètre que le type Object. Autrement dit, il est hors de question
d'utiliser des types primitifs (int, double, ...) avec une collection. Si vous y tenez, vous pourrez faire appel aux
objets "wrappers" tels que Integer ou Double pour pouvoir ajouter des types primitifs de manières indirecte à une
collection.
Parcourir la collection
La classe Vector propose deux manières de parcourir l'ensemble des éléments. La première s'inspire des tableaux:
Vector v = new Vector();
for (int i = 0; i < v.getSize(); i++)
System.out.println(v.getElementAt(i));
Le parcours du Vector ressemble donc vraiment à celui d'un tableau. La syntaxe présentée ci-dessus correspond à celle
du JDK 1.1. Avec le JDK 1.2 (ou l'objet ArrayList), il convient d'écrire ceci:
for (int i = 0; i < v.size(); i++)
System.out.println(v.get(i));
La seconde méthode de parcours invoque les énumérations. Une énumération, en Java, est classe implémentant l'interface
Enumeration. La classe StringTokenizer par exemple fait appel aux énumérations.
Vector v = new Vector();
Enumeration e;
e = v.elements();
while (e.hasMoreElements())
System.out.println(e.nextElement());
Le parcours est ici unidirectionnel. La méthode a son avantage dans certaines situations mais ne permet pas un accès direct
à tel ou tel élément. La collection ArrayList quant à elle fait appel à l'énumération Iterator:
ArrayList a = new ArrayList();
Iterator i;
i = a.iterator();
while (i.hasNext())
System.out.println(i.next());
ArrayList possède également la méthode listIterator retournant un objet ListIterator. Ce dernier possède les
mêmes fonctions que Iterator mais permet en outre des additions, retraits d'éléments et de parcourir les données vers
l'arrière également.
Diverses fonctions
Ces deux collections proposent plusieurs fonctions très pratiques permettant d'extraire des éléments, d'en insérer, de trouver la
place d'un élément, etc... Voici un petit listing répertoriant les fonctions les plus pratiques:
Vector v = new Vector();
v.insertElementAt(obj, position);
v.add(position, obj);
v.removeAllElements();
v.clear();
position = v.indexOf(obj);
v.removeElement(obj);
v.remove(obj);
v.removeElementAt(position);
v.remove(position);
v.setElementAt(obj, position);
v.set(position, obj);
Object[] oo = new Object[v.getSize()];
v.copyInto(oo);
oo = v.toArray();
Ces deux classes font également parties du package java.util de Java. Ces deux objets permettent de créer des tableaux
d'éléments indexés par clés. Ainsi, si l'on associe la clé "Claire" à l'objet "Romain", on pourra obtenir le second objet en
connaissant sa clé. De la même manière que précédemment, Hashtable appartient au JDK 1.1 et HashMap au
JDK 1.2. Et de la même manière encore, HashMap est plus rapide car non thread-safe.
Constructeurs
import java.util.Hashtable;
public class CollectionTest
{
protected Hashtable h = new Hashtable();
protected Hashtable h2 = new Hashtable(200);
}
Aucune explication ne sera ici nécessaire. Le principe de taille et de capacité de Vector s'applique également à nos deux
nouveaux objets.
Ajouter un élément
Pour ajouter un nouvel élément au Hashtable:
Object o = new Object();
Object key = new Object;
hashtable.put(key, o);
Ici aucune différence notable entre Hashtable et HashMap n'est à souligner. L'un comme l'autre possèdent les mêmes
fonctions. Seul Hashtable propose des duplicatas de certaines fonctions, du à son antériorité.
Parcourir les collections
A l'instar de Vector, Hashtable possède deux méthodes pour parcourir la collection. HashMap n'en propose
qu'une. Mais commençons par voir commen récupérer un objet à l'aide de sa clé:
Object o = hashtable.get(key);
Les méthodes elements() et keys() de l'objet Hashtable permettent de parcourir respectivement les objets
et leurs clés à l'aide d'un objet Enumeration. Rien de nouveau ici, donc nous ne nous y attarderons pas. Cependant,
HashMap (et Hashtable) offrent une autre méthode proche de l'utilisation des énumérations:
HashMap h = new HashMap();
Set entries = h.entrySet();
Set keys = h.keySet();
Iterator ie = entries.iterator();
Iterator ik = keys.iterator();
while (ie.hasNext())
System.out.println("Clé: " + ik.next() + " associé à l'objet " + ie.next());
Eh oui, finalement on retrouve nos Iterator vu dans l'objet ArrayList. Seulement ici leur accès est indirect. Les objets
Set intermédiaires sont également des collections proposant quelques méthodes intéressantes telles toArray déjà
évoquée plus haut. Attention, ne vous avisez pas de modifier l'un des objets Set sans faire de même avec l'autre. Vous
risqueriez de vous retrouver confronté à des bugs difficiles à dénicher.
Diverses fonctions
Pour finir, voici une liste de quelques fonctions utiles, applicables aux deux collections que nous étudions:
HashMap h = new HashMap();
h.clear();
boolean exist = h.containsKey(key);
exist = h.containsValue(obj);
exist = h.isEmpty();
h.remove(key);
int keysNumber = h.size();
Avec la pratique, vous découvrirez vite la puissance et l'importance de ces collections. Comme nous l'avons dit dans l'introduction,
de nombreuses autres collections existents. Les piles, les files, les listes liées, doublement liées, etc... autant d'objets qui trouvent
leur importance dans de nombreux programmes. Cependant, les deux types de collections traités dans ce cours restent les plus
fréquemment employés.
|