W3docs

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 an index.
  • 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 mit Objectlist.remove(1) ruft die int-Version auf; list.remove(Integer.valueOf(1)) ruft die Object-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 und set / add am Cursor ausführen kann. Das Kapitel ListIterator behandelt ihn ausführlich.

Bulk-Mutation mit Bezug zur Reihenfolge

  • default void replaceAll(UnaryOperator<E> op)op auf jedes Element in-place anwenden.
  • default void sort(Comparator<? super E> c) — die Liste mit c sortieren (oder natürliche Ordnung, wenn null).
  • boolean addAll(int index, Collection<? extends E> c) — eine ganze Collection an index einfü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 xs

subList 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 first

Das 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 UnsupportedOperationException

Wenn 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.

java— editable, runs on the server

Einige Erkenntnisse aus der Ausgabe, die man sich merken sollte:

  • remove(1) hat 20 entfernt (den Wert an Index 1); remove(Integer.valueOf(10)) hat 10 nach 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.
  • replaceAll behält die Länge der Liste bei und überschreibt jedes Element in place; sort ordnet 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.

Übungen

Übung
`xs` ist `new ArrayList<>(List.of(10, 20, 30, 40))`. Du rufst `xs.subList(1, 3).clear()` auf. Was ist `xs` danach?
`xs` ist `new ArrayList<>(List.of(10, 20, 30, 40))`. Du rufst `xs.subList(1, 3).clear()` auf. Was ist `xs` danach?
Was this page helpful?