Penser en Java 2nde édition | - | Sommaire | Préface | Avant-propos | Chapitre : 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | Annexe : A B C D | Tables des matières | - | Thinking in Java |
Et on ne touche pas au reste du code (une telle généricité peut aussi être réalisée via des itérateurs).
Dans la hiérarchie de classes, on peut voir un certain nombre de classes dont le nom débute par « Abstract », ce qui peut paraître un peu déroutant au premier abord. Ce sont simplement des outils qui implémentent partiellement une interface particulière. Si on voulait réaliser notre propre Set, par exemple, il serait plus simple de dériver AbstractSet et de réaliser le travail minimum pour créer la nouvelle classe, plutôt que d'implémenter l'interface Set et toutes les méthodes qui vont avec. Cependant, la bibliothèque de conteneurs possède assez de fonctionnalités pour satisfaire quasiment tous nos besoins. De notre point de vue, nous pouvons donc ignorer les classes débutant par « Abstract ».
Ainsi, lorsqu'on regarde le diagramme, on n'est réellement concerné que par les interfaces du haut du diagramme et les classes concrètes (celles qui sont entourées par des boîtes solides). Typiquement, on se contentera de créer un objet d'une classe concrète, de la transtyper dans son interface correspondante, et ensuite utiliser cette interface tout au long du code. De plus, on n'a pas besoin de se préoccuper des éléments pré-existants lorsqu'on produit du nouveau code. Le diagramme peut donc être grandement simplifié pour ressembler à ceci :
Il n'inclut plus maintenant que les classes et les interfaces que vous serez amenés à rencontrer régulièrement, ainsi que les éléments sur lesquels nous allons nous pencher dans ce chapitre.
Voici un exemple simple, qui remplit une Collection (représenté ici par une ArrayList) avec des objets String, et affiche ensuite chaque élément de la Collection :
La première ligne de main() crée un objet ArrayList et le transtype ensuite en une Collection. Puisque cet exemple n'utilise que les méthodes de Collection, tout objet d'une classe dérivée de Collection fonctionnerait, mais l'ArrayList est la Collection à tout faire typique.
La méthode add(), comme son nom le suggère, ajoute un nouvel élément dans la Collection. En fait, la documentation précise bien que add() « assure que le conteneur contiendra l'élément spécifié ». Cette précision concerne les Sets, qui n'ajoutent un élément que s'il n'est pas déjà présent. Avec une ArrayList, ou n'importe quel type de List, add() veut toujours dire « stocker dans », parce qu'une List se moque de contenir des doublons.
Toutes les Collections peuvent produire un Iterator grâce à leur méthode iterator(). Ici, un Iterator est créé et utilisé pour traverser la Collection, en affichant chaque élément.
La table suivante contient toutes les opérations définies pour une Collection (sans inclure les méthodes directement héritées de la classe Object), et donc pour un Set ou une List (les Lists possèdent aussi d'autres fonctionnalités). Les Maps n'héritant pas de Collection, elles seront traitées séparément.
boolean add(Object) | Assure que le conteneur stocke l'argument. Renvoie false si elle n'ajoute pas l'argument (c'est une méthode « optionnelle », décrite plus tard dans ce chapitre). |
boolean addAll(Collection) | Ajoute tous les éléments de l'argument. Renvoie true si un élément a été ajouté (« optionnelle »). |
void clear() | Supprime tous les éléments du conteneur (« optionnelle »). |
boolean contains(Object) | true si le conteneur contient l'argument. |
boolean containsAll(Collection) | true si le conteneur contient tous les éléments de l'argument. |
boolean isEmpty() | true si le conteneur ne contient pas d'éléments. |
Iterator iterator() | Renvoie un Iterator qu'on peut utiliser pour parcourir les éléments du conteneur. |
boolean remove(Object) | Si l'argument est dans le conteneur, une instance de cet élément est enlevée. Renvoie true si c'est le cas (« optionnelle »). |
boolean removeAll(Collection) | Supprime tous les éléments contenus dans l'argument. Renvoie true si au moins une suppression a été effectuée (« optionnelle »). |
boolean retainAll(Collection) | Ne garde que les éléments contenus dans l'argument (une « intersection » selon la théorie des ensembles). Renvoie true s'il y a eu un changement (« optionnelle »). |
int size() | Renvoie le nombre d'éléments dans le conteneur. |
Object[] toArray() | Renvoie un tableau contenant tous les éléments du conteneur. |
Object[] toArray(Object[] a) | Renvoie un tableau contenant tous les éléments du conteneur, dont le type est celui du tableau a au lieu d'Objects génériques (il faudra toutefois transtyper le tableau dans son type correct). |
Notez qu'il n'existe pas de fonction get() permettant un accès aléatoire. Ceci parce que les Collections contiennent aussi les Sets, qui maintiennent leur propre ordre interne, faisant de toute tentative d'accès aléatoire un non-sens. Il faut donc utiliser un Iterator pour parcourir tous les éléments d'une Collection ; c'est la seule façon de récupérer les objets stockés.
L'exemple suivant illustre toutes ces méthodes. Encore une fois, cet exemple marcherait avec tout objet héritant de Collection, mais nous utilisons ici une ArrayList comme « plus petit dénominateur commun » :
Les ArrayLists sont créées et initialisées avec différents ensembles de données, puis transtypées en objets Collection ; il est donc clair que seules les fonctions de l'interface Collection sont utilisées. main() réalise de simples opérations pour illustrer toutes les méthodes de Collection.
Les sections suivantes décrivent les diverses implémentations des Lists, Sets et Maps et indiquent dans chaque cas (à l'aide d'une astérisque) laquelle devrait être votre choix par défaut. Vous noterez que les classes pré-existantes Vector, Stack et Hashtable ne sont pas incluses car certains conteneurs Java 2 fournissent les mêmes fonctionnalités.
La List de base est relativement simple à utiliser, comme vous avez pu le constater jusqu'à présent avec les ArrayLists. Mis à part les méthodes courantes add() pour insérer des objets, get() pour les retrouver un par un, et iterator() pour obtenir un Iterator sur la séquence, les listes possèdent par ailleurs tout un ensemble de méthodes qui peuvent se révéler très pratiques.
Les Lists sont déclinées en deux versions : l'ArrayList de base, qui excelle dans les accès aléatoires aux éléments, et la LinkedList, bien plus puissante (qui n'a pas été conçue pour un accès aléatoire optimisé, mais dispose d'un ensemble de méthodes bien plus conséquent).
List (interface) | L'ordre est la caractéristique la plus importante d'une List ; elle garantit de maintenir les éléments dans un ordre particulier. Les Lists disposent de méthodes supplémentaires permettant l'insertion et la suppression d'éléments au sein d'une List (ceci n'est toutefois recommandé que pour une LinkedList). Une List produit des ListIterators, qui permettent de parcourir la List dans les deux directions, d'insérer et de supprimer des éléments au sein de la List. |
ArrayList* | Une List implémentée avec un tableau. Permet un accès aléatoire instantané aux éléments, mais se révèle inefficace lorsqu'on insère ou supprime un élément au milieu de la liste. Le ListIterator ne devrait être utilisé que pour parcourir l'ArrayList dans les deux sens, et non pour l'insertion et la suppression d'éléments, opérations coûteuses comparées aux LinkedLists. |
LinkedList | Fournit un accès séquentiel optimal, avec des coûts d'insertion et de suppression d'éléments au sein de la List négligeables. Relativement lente pour l'accès aléatoire (préférer une ArrayList pour cela). Fournit aussi les méthodes addFirst(), addLast(), getFirst(), getLast(), removeFirst() et removeLast() (qui ne sont définies dans aucune interface ou classe de base) afin de pouvoir l'utiliser comme une pile, une file (une queue) ou une file double (queue à double entrée). |
Les méthodes dans l'exemple suivant couvrent chacune un groupe de fonctionnalités : les opérations disponibles pour toutes les listes (basicTest()), le déplacement dans une liste avec un Iterator (iterMotion()) ainsi que la modification dans une liste avec un Iterator (iterManipulation()), la visualisation des manipulations sur la List (testVisual()) et les opérations disponibles uniquement pour les LinkedLists.