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.
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.
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.
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)
.
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.
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") ;
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) ; }
un un et demi deux deux et demi trois trois et demi