EMZETT.
Login

ArrayList

Kurz: Die gebräuchlichste List-Implementierung in Java — intern ein dynamisch wachsendes Array, das bei Bedarf automatisch vergrößert wird.

Genauer: Zugriff per Index (get(i)) ist sehr schnell (konstante Zeit), Einfügen/Entfernen in der Mitte dagegen langsam, da nachfolgende Elemente verschoben werden müssen. Im Gegensatz zu einem klassischen Array muss die Größe nicht vorher feststehen.

List<String> namen = new ArrayList<>();
namen.add("Anna");
namen.get(0);

Im Detail

Intern hält eine ArrayList ein normales Array als Backing-Array. Ist dieses voll und ein weiteres Element soll hinzugefügt werden, erzeugt Java automatisch ein neues, größeres Array (typischerweise das 1,5-fache der alten Größe) und kopiert alle Elemente hinüber — für den Aufrufer unsichtbar, aber nicht kostenlos. Wer die ungefähre Endgröße schon kennt, spart sich wiederholtes Vergrößern mit dem Konstruktor new ArrayList<>(erwarteteGroesse).

List<String> namen = new ArrayList<>();
namen.add("Anna");
namen.add("Ben");
namen.add(0, "Erster"); // Einfügen an Position 0 - verschiebt alles danach
 
namen.get(0);      // "Erster" - O(1), sehr schnell
namen.remove(0);   // entfernt "Erster" - O(n), alle folgenden rücken auf
namen.contains("Ben"); // true - O(n), durchsucht linear
 
for (String name : namen) { // For-Each nutzt intern einen Iterator
    System.out.println(name);
}

Wichtige Fallstricke: ArrayList ist NICHT threadsicher (mehrere Threads dürfen sie nicht gleichzeitig verändern, sonst drohen ConcurrentModificationException oder Datenkorruption — dafür gibt es Collections.synchronizedList() oder CopyOnWriteArrayList). Und: Elemente während einer laufenden For-Each-Schleife direkt über liste.remove() zu entfernen wirft eine ConcurrentModificationException — dafür braucht es stattdessen iterator.remove() über einen expliziten Iterator.

Häufige Operationen im Überblick

List<Integer> zahlen = new ArrayList<>(List.of(5, 3, 8));
 
zahlen.set(0, 99);          // ersetzt Element an Index 0
zahlen.indexOf(8);          // 2 - erste Fundstelle, -1 wenn nicht vorhanden
zahlen.isEmpty();           // false
zahlen.size();              // 3
zahlen.clear();             // leert die Liste komplett
Collections.sort(zahlen);   // sortiert in-place (siehe auch Sorting)

ArrayList vs. LinkedList

Die zweite gängige List-Implementierung ist LinkedList, intern eine doppelt verkettete Liste. Bei ArrayList ist wahlfreier Zugriff (get(i)) sehr schnell (O(1)), Einfügen am Anfang/in der Mitte dagegen langsam (O(n), da Elemente verschoben werden müssen). Bei LinkedList ist es umgekehrt: Einfügen am Anfang/Ende ist O(1) (nur Zeiger umbiegen), aber get(i) muss die Kette von vorne durchlaufen (O(n)). In der Praxis ist ArrayList fast immer die bessere Standardwahl — moderne CPUs bevorzugen den zusammenhängenden Speicherbereich eines Arrays (Cache-Lokalität), LinkedList lohnt sich nur bei sehr häufigem Einfügen/Entfernen an beiden Enden ohne wahlfreien Zugriff.

Generics und Autoboxing

ArrayList<Integer> speichert intern keine primitiven int-Werte, sondern Integer-Objekte — jedes Element wird beim Einfügen automatisch “geboxt” (int → Integer) und beim Auslesen wieder “unboxt”. Das kostet bei sehr großen Listen mit primitiven Zahlen spürbar Speicher und Performance im Vergleich zu einem rohen int[]-Array — ein Grund, warum performancekritischer Code manchmal bewusst auf Arrays statt Collections zurückgreift.

Siehe auch: List, LinkedList, Arrays, Iterator, Generics