3. Interface List

3.1. Notion de List

L'interface List modèlise une liste indexée par des entiers. Lorsque l'on ajoute un objet à une liste, il prend un numéro d'ordre, géré par la liste. Lorsque l'on en retire un, il est de la responsabilité de l'implémentation de la liste de conserver une numérotation cohérente. On peut donc toujours demander le n ième élément d'une liste. L'interface List étend Collection, et lui ajoute essentiellement deux types de méthodes : celles qui permettent de manipuler les objets directement à partir de leur numéro d'ordre, et celles qui permettent de parcourir la liste dans un sens ou dans l'autre.

3.2. Détail des méthodes disponibles

Voyons les méthodes que List ajoute à Collection.
  • add(int index, T t) et addAll(int index, Collection<? extends T> collection) : permettent d'insérer un ou plusieurs éléments à la position notée par index.
  • set(int index, T t) : permet de remplacer l'élément placé à la position index par celui passé en paramètre. L'élément qui existait est retiré de la liste, et retourné par cette méthode.
  • get(int index) : retourne l'élément placé à l'index passé en paramètre.
  • remove(int index) : retire l'élément placé à l'index passé en paramètre. Cet élément est retourné par la méthode.
  • indexOf(Object o) et lastIndexOf(Object o) : retournent respectivement le premier et le dernier index de l'objet passé en paramètre dans cette liste.
  • subList(int debut, int fin) : retourne la liste composé des éléments compris entre l'index debut, et l'index fin - 1. Notons que cette sous-liste n'est pas une copie de la liste existante, mais une vue sur cette liste. Toutes les modifications de la liste principale sont donc vues au travers de cette sous-liste, et réciproquement. Nous verrons cela sur un exemple simple.

3.3. Interface ListIterator

L'interface List supporte bien sûr la méthode iterator(), mais elle ajoute une autre méthode analogue, listIterator(). Cette méthode retourne un objet de type ListIterator, qui est une interface, extension de Iterator. ListIterator permet de parcourir une liste dans le sens croissant de ses index, ou dans le sens décroissant, et ajoute quelques méthodes supplémentaires.
  • hasPrevious() : retourne un booléen qui est vrai s'il existe un élément à itérer dans l'ordre décroissant des index.
  • previous() : retourne l'élément précédent.
  • previousIndex() et nextIndex() : retournent respectivement l'index de l'élément précédent, et l'index de l'élément suivant.
L'interface ListIterator permet aussi de manipuler directement la liste que l'on est en train d'itérer. Ces méthodes sont définies dans l'interface, mais sont facultatives. Une implémentation qui choisirait de ne pas les supporter doit alors jeter une exception de type UnsupportedOperationException lors de leurs appels.
  • add(T t) : permet d'insérer un élément dans la liste à l'endroit où l'on se trouve, c'est-à-dire avant l'élément qui aurait été retourné par un appel à next(). Notons que si l'on fait un appel à next() après une telle insertion, ce n'est pas l'élément que l'on vient d'insérer qui est retourné, mais l'élément suivant.
  • remove(T t) : permet de retirer de la liste l'élément que l'on vient d'itérer. Cette méthode ne peut être appelée qu'après un appel à next() ou previous(). Il n'est donc pas légal de faire plusieurs appel de suite à remove(T t). Il est illégal d'appeler cette méthode juste après un appel à add(T t) ou remove(t t).
  • set(T t) : remplace l'élément que l'on vient d'itérer par l'élément passé en paramètre. Cette méthode ne peut être appelée qu'après un appel à next() ou previous(). Il est illégal d'appeler cette méthode juste après un appel à add(T t) ou remove(t t).

3.4. Implémentations, exemples d'utilisation

L'interface List possède trois implémentations standard dans l'API Java Collection : Vector, ArrayList et LinkedList. La classe Vector est présente depuis Java 1.0, et est synchronisée. Comme les noms de ces classes le laisse supposer, ArrayList est construite sur un tableau, et LinkedList sur une liste chaînée. Ces deux implémentations exposent bien sûr les mêmes fonctionnalités et la même sémantique. Cela dit, elles ne doivent pas être utilisées dans les mêmes cas. Un tableau permet d'accéder très rapidement à un élément donné si l'on possède son index, ce qui n'est pas le cas d'une liste chaînée. En revanche, l'insertion d'un élément dans un tableau est un processus lourd (il faut décaler des éléments du tableau). Dans une liste chaînée ce processus est rapide : il s'agit juste d'un mouvement de pointeurs. Enfin, augmenter la capacité d'un tableau est également un processus lourd, alors qu'une liste chaînée, par définition, n'a pas de capacité maximale. Le type d’implémentation sera donc choisi en fonction du type de problème que l’on a à traiter. D’une façon générale, certaines opérations ensemblistes sont plus rapides sur les listes chaînées, alors que les accès aux éléments individuels sont plus efficaces sur les tableaux.

3.4.1. Création d'une liste de String

Créons une liste de chaînes de caractères, comme pour notre collection.

Exemple 6. Création d'une liste de String

// création d'une liste de String
List<String> liste =  new ArrayList<String>() ;

 // ajout d'éléments à cette liste
liste.add("un") ;
liste.add("deux") ;
liste.add("trois") ;

 // ajout d'un élément à un index
liste.add(1,  "avant deux") ;

 // positionnement d'un élément donné
liste.set(3,  "TROIS") ;

3.4.2. Parcourir les éléments d'une liste avec un ListIterator

On peut parcourir les éléments d'une liste comme s'il s'agissait d'une collection. Voyons l'utilisation du listIterator.

Exemple 7. Utilisation du ListIterator

// dans une méthode main
 // création d'une liste
List<String> liste =  new ArrayList<String>() ;
liste.add("un") ;
liste.add("deux") ;
liste.add("trois") ;

 // création d'un listIterator sur cette liste
ListIterator<String> it = liste.listIterator() ;

 while(it.hasNext()) {
    // on ajoute un élément supplémentaire après chaque
    // élément de la liste
   String element = it.next() ;
   it.add(element +  " et demi") ;
}

 // vérification du résultat
 for (String s : liste) {
   System.out.println(s) ;
}

Ce code affiche bien :
un
un et demi
deux
deux et demi
trois
trois et demi
Java API avancées
Retour au blog Java le soir
Cours & Tutoriaux
Table des matières
API Collection
1. Introduction
2. Interface Collection
2.1. Notion de Collection
2.2. Détail des méthodes disponibles
2.3. Interface Iterator
2.4. Implémentation, exemples d'utilisation
3. Interface List
3.1. Notion de List
3.2. Détail des méthodes disponibles
3.3. Interface ListIterator
3.4. Implémentations, exemples d'utilisation
4. Interface Set
4.1. Notion de Set
4.2. Implémentations HashSet et LinkedHashSet
4.3. Exemples d'utilisation
5. Interface SortedSet
5.1. Notion de SortedSet
5.2. Détails des méthodes disponibles
5.3. Exemples d'utilisation
6. Interface NavigableSet
6.1. Notion de NavigableSet
6.2. Détails des méthodes disponibles
6.3. Exemple d'utilisation
7. Interfaces Queue et Deque
7.1. Notion de file d'attente
7.2. Détail des méthodes disponibles
7.3. Utilisation des interfaces Queue et Deque
8. Tables de hachage
8.1. Notion de table de hachage
8.2. Interface Map
8.3. Interface Map.Entry
8.4. Interface SortedMap
8.5. Interface NavigableMap
8.6. Implémentations
8.7. Exemples d'utilisation
9. Classes utilitaires Collections et Arrays
9.1. Introduction
9.2. Classe Arrays
9.3. Classe Collections
Génériques
1. Introduction
2. Un premier exemple
2.1. Une première classe générique
2.2. Une première méthode générique
3. Contraindre un type générique
3.1. Problème posé
3.2. Contraindre un type générique
4. Implémentation des génériques
4.1. Type erasure
4.2. Types génériques et casts
4.3. Type générique et exception
4.4. Construction d'une instance générique
4.5. Génériques et membres statiques
4.6. Collisions de méthodes génériques
4.7. Implémentation de plusieurs types identiques
5. Type <?>
5.1. Introduction
5.2. Type ? extension d'un type
5.3. Type ? super-type d'un type
Expressions régulières
1. Introduction
2. Mise en œuvre des expressions régulières
2.1. Fonctionnement d'une regexp
2.2. Fonctionnement de l'API en Java
2.3. Un premier exemple
2.4. Classe Pattern
2.5. Classe Matcher
2.6. Utilisation des méthode find() et group()
2.7. Méthodes de remplacement
2.8. Sélection de régions
3. Syntaxe des expressions régulières
3.1. Notion de classe
3.2. Étude d'un cas réel
3.3. Recherche d'un mot précis
3.4. Recherche de deux mots précis
3.5. Recherche d'un mot commençant par une lettre donnée
3.6. Cas de mots comportant des caractères accentués
3.7. Recherche sur les lignes
Introspection
1. Introduction
2. La classe Class
2.1. Utilisation de Class
2.2. Méthodes disponibles
2.3. Remarque sur la propriété Accessible
2.4. Type d'une classe
2.5. Création d'une instance à partir d'un objet Class
2.6. Cas des énumérations
3. Les classes Method et Constructor
3.1. Utilisation de Method
3.2. Utilisation de Constructor
3.3. Méthodes disponibles
3.4. Invocation d'une méthode par introspection
4. La classe Field
4.1. Utilisation de Field
4.2. Méthodes disponibles
4.3. Accès à un champ par introspection
5. La classe Modifier
Programmation concurrente
1. Introduction
2. Lançons nos premiers threads
2.1. Introduction
2.2. Un premier thread, extension de Thread
2.3. Un deuxième thread, implémentation de Runnable
2.4. Remarque sur la méthode Thread.sleep(long)
2.5. Arrêter un thread
3. Concurrence d'accès
3.1. Notion d'état
3.2. Exemple de concurrence d'accès sur un état
3.3. Analyse de la concurrence d'accès
3.4. Solution au problème
3.5. Champs volatile
4. Synchronisation
4.1. Définition d'un bloc synchronisé
4.2. Fonctionnement d'un bloc synchronisé
4.3. Notion de deadlock
4.4. Bonnes pratiques pour la synchronisation de threads
5. Opérations atomiques
5.1. Atomicité d'une opération
5.2. Solutions disponibles
5.3. Variables atomiques
6. Collections synchronisées et concurrentes
6.1. Introduction
6.2. Position du problème
6.3. Solutions proposées
7. Files d'attente
7.1. Introduction, pattern producteur / consommateur
7.2. Interface BlockingQueue<E>
7.3. Implémentations de BlockingQueue
7.4. Exemple de producteur / consommateur
7.5. Arrêter un producteur / consommateur : pilule empoisonnée
8. Classes utilitaires de l'API Concurrent
8.1. Introduction
8.2. Énumération TimeUnit
8.3. Interface Callable<V>
8.4. Interfaces Future<V> et RunnableFuture<V>
8.5. Interface ScheduledFuture<V> et RunnableScheduledFuture<V>
9. Pattern executor
9.1. Notion de réserve de threads
9.2. Interface Executor
9.3. Interface ExecutorService
9.4. Interface ScheduledExecutorService
9.5. Classe Executors
9.6. Pattern de lancement de tâches
10. Classes de contrôle d'accès
10.1. Introduction
10.2. Interfaces Lock et ReadWriteLock
10.3. Notion de verrou réentrant
10.4. Classe RentrantLock
10.5. Classe ReadWriteRentrantLock
11. Sémaphores, barrières et latches
11.1. Introduction
11.2. Notion de sémaphore, classe Semaphore
11.3. Notion de latch, classe CountDownLatch
11.4. Notion de barrière, classe CyclicBarrier