Etudions la mise en oeuvre d’une interface simplifiée pour les listes:
public interface SList<E> extends Iterable<E> {
int size();
boolean isEmpty();
void add(int i, E e);
void remove(int i);
boolean contains(E e);
E get(int i);
E set(int i, E e);
Iterator<E> iterator();
}Liste abstraite
On peut déjà mettre en oeuvre des fonctions qui ne devraient pas changer selon l’implémentation de différentes listes, notamment
isEmpty()
boolean isEmpty() {
return size() == 0;
}contains()
boolean contains(E e) {
for (E el : this) {
if (el.equals(e)) return true;
}
return false;
}toString()
public String toString() {
StringJoiner j = new StringJoiner(", ", "[", "]");
for (E e: this)
j.add(e.toString());
return j.toString();
}On peut également implémenter des préconditions pour vérifier si les index passés sont valides
Tableau-liste
Ces listes se basent sur le contenu d’un tableau. Par contre, vu que le tableau contiendra des cases vides, il faut stocker la taille indépendamment.
private int size = 0;
private E[] array = (E[]) new Object[10];
public int size() {
return size;
}On doit par contre vérifier que l’on ait assez de capacité pour pouvoir insérer des choses dans le tableau
public void add(int i, E e) {
checkPositionIndex(i); // vérifier si l'index est valide
if (size < array.length) {
System.arraycopy(array, i, array, i + 1, size - i);
} else {
E[] newArray = (E[]) new Object[array.length * 2];
System.arraycopy(array, 0, newArray, 0, i);
System.arraycopy(array, i, newArray, i + 1, size - 1);
array = newArray;
}
array[i] = e;
size++;
}Pour enlever un élément, on doit ensuite déplacer les éléments se situant plus loin dans le tableau plus bas.
public void remove(int i) {
checkElementIndex(i); // Vérifier s'il y a qqch à l'index
System.arraycopy(array, i + 1, array, i, size - i - 1);
array[--size] = null;
}Et on peut facilement accéder aux éléments du tableau
public E get(int i) {
return array[checkElementIndex(i)];
}Et également facilement modifier un élément
public E set(int i, E e) {
E oldE = array[checkElementIndex(i)];
array[i] = e;
return oldE;
}Itération
Pour pouvoir itérer, il faut implémenter l’accès au éléments du tableau. Pour simplifier l’implémentation, on imbrique statiquement une mise en oeuvre de l’itérateur
private static final class SALIterator1<E> implements Iterator<E> {
private final SArrayList<E> list;
private int nextI;
private boolean canRemove;
public SALIterator1(SArrayList<E> list) {
this.list = list;
this.nextI = 0;
this.canRemove = false;
}
@Override
public boolean hasNext() {
return nextI < list.size;
}
@Override
public E next() {
if (! hasNext())
throw new NoSuchElementException();
canRemove = true;
return list.array[nextI++];
}
@Override
public void remove() {
if (! canRemove)
throw new IllegalStateException();
canRemove = false;
list.remove(--nextI);
}
}Ainsi on peut retourner un itérateur si demandé.
public Iterator<E> iterator() {
return new SALIterator1<>(this);
}On pourrait également retourner une classe anonyme d’itérateur.
public Iterator<E> iterator() {
return new Iterator<E>() {
private int nextI = 0;
private boolean canRemove = false;
@Override
public boolean hasNext() {
return nextI < size;
}
@Override
public E next() {
if (!hasNext())
throw new NoSuchElementException();
canRemove = true;
return array[nextI++];
}
@Override
public void remove() {
if (!canRemove)
throw new IllegalStateException();
SArrayList.this.remove(--nextI);
canRemove = false;
}
};
}Liste chaînée
Ou LinkedList en java — ne stocke pas les éléments dans un tableau, mais contient des nœuds reliés entre eux. ⇒ Ils sont donc liés par des références les uns aux autres.
On a trois différentes manières d’implémenter ces listes:
- Liste simple — chaque noeud fait référence au prochain
- Liste doublement chaînée — chaque noeud référence le noeud suivant et précédent
- Liste circulaire — le premier et dernier noeud sont reliés entre eux
Cette implémentation contient donc une classe imbriquée statique qui représente les noeuds:
private static final class Node<E> {
private Node<E> next;
private E element;
public Node(Node<E> next, E element) {
this.next = next;
this.element = element;
}
}L’ajout et la suppression sont plutôt triviaux, car il ne faut jamais re-allouer le tableau pour qu’il ait plus de taille.