Java List Interface
Geordnete, indexbasierte Collections in Java mit dem List-Interface und seinen wichtigsten Operationen.
List<E> ist Collection<E> mit zwei zusätzlichen Garantien: Die Elemente haben eine definierte Reihenfolge, und diese Reihenfolge ist über einen Integer-Index adressierbar. Sobald Reihenfolge und Index vorhanden sind, werden eine ganze Klasse von Methoden sinnvoll — get(i), set(i, x), indexOf(x), subList(from, to), Sortierung, Iteration in umgekehrter Reihenfolge. Dieses Kapitel behandelt den Vertrag; die Implementierungen (ArrayList, LinkedList, Vector) folgen unmittelbar danach mit ihren eigenen Performance-Kompromissen.
Was „geordnet" hier bedeutet
„Geordnet" bei einer List bedeutet, dass die Einfügereihenfolge erhalten bleibt — Index 0 ist das erste Element, das eingefügt wurde, Index size() - 1 ist das letzte, und das Hinzufügen eines neuen Elements am Ende verschiebt nichts. Es bedeutet nicht „sortiert" — eine Liste behält die Reihenfolge, in der sie befüllt wurde. Wenn man sortierte Iteration möchte, ruft man entweder Collections.sort(list) auf (was die Liste verändert), oder man verwendet von Anfang an TreeSet / TreeMap. Diese beiden Konzepte sollten nicht verwechselt werden.
Duplikate sind erlaubt. [1, 1, 2, 1] ist eine vollkommen gültige List<Integer>.
Die Methoden, die List gegenüber Collection hinzufügt
Alles, was Collection deklariert, ist weiterhin vorhanden — add, remove, contains, size usw. List fügt dann positionsbezogene und reihenfolgebewusste Operationen hinzu:
Positioneller Zugriff
E get(int index)— Element anindex.E set(int index, E element)— ersetzen, gibt den alten Wert zurück.void add(int index, E element)— einfügen (verschiebt spätere Elemente nach rechts).E remove(int index)— nach Position entfernen (gibt das entfernte Element zurück). Beachte die Überladung mitObject—list.remove(1)ruft dieint-Version auf;list.remove(Integer.valueOf(1))ruft dieObject-Version auf.
Suche
int indexOf(Object o)— erstes Vorkommen oder-1.int lastIndexOf(Object o)— letztes Vorkommen oder-1.
Teilansichten und Iteration
List<E> subList(int fromIndex, int toIndex)— eine Live-Ansicht eines Bereichs. Änderungen daran ändern die zugrundeliegende Liste (und umgekehrt). Halboffenes Intervall:[from, to).ListIterator<E> listIterator()/listIterator(int index)— Iterator, der auch rückwärts laufen, den aktuellen Index abfragen undset/addam Cursor ausführen kann. Das Kapitel ListIterator behandelt ihn ausführlich.
Bulk-Mutation mit Bezug zur Reihenfolge
default void replaceAll(UnaryOperator<E> op)—opauf jedes Element in-place anwenden.default void sort(Comparator<? super E> c)— die Liste mitcsortieren (oder natürliche Ordnung, wennnull).boolean addAll(int index, Collection<? extends E> c)— eine ganze Collection anindexeinfügen.
Factories (Java 9+)
List.of(...)— eine unveränderliche Liste der angegebenen Elemente. Kompakt, allokationsfrei für kleine Größen.List.copyOf(Collection)— ein unveränderlicher Schnappschuss einer anderen Collection.
Gleichheit bei List ist reihenfolgesensitiv
Zwei Listen sind gleich genau dann, wenn sie dieselbe Größe haben, in derselben Reihenfolge, mit gleichen Elementen an jedem Index. List.of(1, 2) ist nicht gleich List.of(2, 1), auch wenn sie als Sets gleich wären. Das ist eine feste Regel aus dem List-Vertrag — wenn zwei Listen verglichen werden und unerwartet false zurückgegeben wird, sollte zuerst die Reihenfolge geprüft werden.
subList ist eine Ansicht, keine Kopie
Das stolpert fast jeden Lernenden einmal:
List<Integer> xs = new ArrayList<>(List.of(0, 1, 2, 3, 4, 5));
List<Integer> middle = xs.subList(2, 5); // [2, 3, 4]
middle.set(0, 99);
System.out.println(xs); // [0, 1, 99, 3, 4, 5] — xs changed!
middle.clear();
System.out.println(xs); // [0, 1, 5] — gone from xssubList gibt ein Live-Fenster zurück. Lese- und Schreibzugriffe gehen direkt zur zugrundeliegenden Liste. Das ist äußerst nützlich für In-place-Algorithmen — einen Bereich löschen, sortieren, einfügen — bedeutet aber auch, dass man keine subList-Referenz aufbewahren und dann die übergeordnete Liste über einen anderen Pfad verändern kann. Die Javadoc besagt, dass strukturelle Änderungen an der zugrundeliegenden Liste außerhalb der Sub-Liste das Verhalten der Sub-Liste „undefiniert" machen. In der Praxis führt das zu einer ConcurrentModificationException beim nächsten Aufruf.
Wenn man einen unabhängigen Ausschnitt benötigt, sollte man ihn kopieren: new ArrayList<>(xs.subList(2, 5)).
Die zwei remove-Überladungen
Ein häufiger Fehler:
List<Integer> nums = new ArrayList<>(List.of(10, 20, 30));
nums.remove(1); // removes index 1 → [10, 30]
nums.remove(Integer.valueOf(10)); // removes the value 10 → [30]Die int-Überladung gewinnt, weil int spezifischer ist als Integer. Wenn man „den Wert 10 entfernen" meint, muss man ihn explizit boxen. Dies ist eine der wenigen Stellen in der Sprache, an der die Autoboxing-Regeln und die Überladungsauflösung aktiv in Konflikt geraten.
In-place Sortierung
list.sort(comparator) verändert die Liste. null übergeben, um die natürliche Ordnung der Elemente (ihr Comparable) zu verwenden; andernfalls einen Comparator übergeben. Dies ist die moderne Form — Collections.sort(list) funktioniert noch und ist identisch, aber list.sort(...) ist die Default-List-Methode:
List<String> names = new ArrayList<>(List.of("Linus", "Ada", "Grace"));
names.sort(null); // natural: ["Ada", "Grace", "Linus"]
names.sort(Comparator.comparingInt(String::length)); // shortest firstDas Kapitel Comparable / Comparator weiter unten in diesem Teil erklärt, was null bedeutet und wie man Comparatoren für eigene Typen erstellt.
Immutable Factories: wenn add eine Exception wirft
List.of(...), List.copyOf(...) und die von Collectors.toUnmodifiableList() zurückgegebenen Listen sind unveränderlich. Sie lehnen jeden verändernden Aufruf mit UnsupportedOperationException ab. Sie akzeptieren auch keine null-Elemente. Sie sind ideal für schreibgeschützte Daten, die weit verteilt werden:
List<String> CONSTANTS = List.of("red", "green", "blue");
CONSTANTS.add("yellow"); // throws UnsupportedOperationExceptionWenn spätere Veränderungen möglich sein sollen, sollte man mit new ArrayList<>(List.of(...)) beginnen.
Ein ausgearbeitetes Beispiel: jede List-spezifische Methode
Das folgende Programm demonstriert die Methoden, die List über Collection hinaus hinzufügt. Die subList-Mutation, die Überladungsfalle und der Unterschied zwischen sort und replaceAll werden sichtbar.
Einige Erkenntnisse aus der Ausgabe, die man sich merken sollte:
remove(1)hat20entfernt (den Wert an Index 1);remove(Integer.valueOf(10))hat10nach Wert entfernt. Derselbe Methodenname, zwei verschiedene Aufgaben — abhängig vom statischen Typ des Arguments.- Nach
mid.clear()ist die übergeordnete Liste[0, 1, 5]. Die Ansicht war der Bereich — das Löschen entfernte diese Elemente aus dem zugrundeliegenden Array. replaceAllbehält die Länge der Liste bei und überschreibt jedes Element in place;sortordnet das Vorhandene neu an. Beide lassen sich gut kombinieren.
Nächste Schritte
Der List-Vertrag ist jetzt bekannt — was garantiert wird, was was verändert, wo die Fallstricke liegen. Als nächstes folgt die Implementierung, die in 90 % der Fälle verwendet wird: das resizable-array-basierte ArrayList. Gleicher Vertrag, spezifische Performance-Eigenschaften und einige eigene Extras.