Von Elyndra Valen, Senior Entwicklerin bei Java Fleet Systems Consulting
Mit Einblicken von Nova Trent (Junior Dev) und Jamal Hassan (Backend Developer)
Schwierigkeit: 🟢 Einsteiger | 🟡 Mittel
Lesezeit: 25 Minuten
Voraussetzungen: Java OOP abgeschlossen (Klassen, Vererbung, Interfaces, Exceptions)
Kurs: Java Erweiterte Techniken – Tag 1 von 10
📖 Java Erweiterte Techniken – Alle Tage
📍 Du bist hier: Tag 1
⚡ Das Wichtigste in 30 Sekunden
Dein Problem: Arrays sind statisch. Du weißt vorher nicht immer, wie viele Elemente du brauchst. Elemente einfügen oder löschen? Ein Albtraum.
Die Lösung: Das Java Collections Framework – dynamische Datenstrukturen, die mit deinen Anforderungen wachsen.
Heute lernst du:
- ✅ Warum Collections Arrays in den meisten Fällen überlegen sind
- ✅ Wie
ArrayListundLinkedListfunktionieren – und wann du welche nutzt - ✅ Immutable Lists mit
List.of()erstellen - ✅ Verschiedene Wege, durch Listen zu iterieren
Für wen ist dieser Artikel?
- 🌱 Anfänger: Du lernst Collections von Grund auf
- 🌿 Erfahrene: Du vertiefst Best Practices und Performance-Unterschiede
- 🌳 Profis: Im Bonus findest du Big-O Analyse und Edge Cases
Zeit-Investment: 25 Minuten Lesen + 30-60 Minuten Praxis
👋 Elyndra: „Arrays haben mir auch mal gereicht…“
Hi! 👋
Elyndra hier. Ich erinnere mich noch gut an meine ersten Java-Projekte. Arrays waren meine besten Freunde. String[] namen = new String[10]; – simpel, direkt, fertig.
Bis ich eine Benutzerliste bauen musste, bei der User sich registrieren und abmelden konnten.
Kennst du das? Du deklarierst ein Array mit 10 Plätzen. Dann kommen 11 User. Oder du willst den dritten User löschen und musst alle nachfolgenden Elemente verschieben. Manuell. Mit einer Schleife.
Ngl, das war der Moment, als ich dachte: „Es muss doch einen besseren Weg geben.“
Gibt es. Und heute zeige ich dir genau diesen Weg.
Lass uns das gemeinsam angehen! 🚀
🖼️ Das Konzept auf einen Blick

Abbildung 1: Das komplette Java Collections Framework – Interfaces, Klassen und Legacy-Komponenten
🟢 GRUNDLAGEN
Was ist das Collections Framework?
Das Java Collections Framework ist eine Sammlung von Interfaces und Klassen, die dir dynamische Datenstrukturen bieten. Stell es dir vor wie einen Werkzeugkasten – statt nur einem Schraubenzieher (Array) hast du jetzt Schraubenzieher, Zangen, Hämmer und Sägen.
Die vier Haupttypen:
| Typ | Beschreibung | Beispiel |
|---|---|---|
| List | Geordnete Sammlung mit Index-Zugriff | Einkaufsliste, Playlist |
| Set | Keine Duplikate erlaubt | Unique User-IDs, Tags |
| Map | Key-Value-Paare | Telefonbuch, Konfiguration |
| Queue | First-In-First-Out | Warteschlange, Task-Queue |
Heute fokussieren wir uns auf List – die vielseitigste und am häufigsten verwendete Collection.
Modern vs. Legacy – Was sollst du verwenden?
💬 Nova fragt: „Ich hab in älterem Code
VectorundStackgesehen. Soll ich die auch lernen?“
Kurze Antwort: Nein. Diese Klassen sind Legacy aus Java 1.0 – also über 25 Jahre alt.
🟢 MODERN – Diese verwenden:
| Klasse | Verwendung | Warum? |
|---|---|---|
ArrayList | Standard für Listen | Schnell, flexibel, 90% aller Fälle |
LinkedList | Wenn Deque-Operationen nötig | addFirst/addLast in O(1) |
ArrayDeque | Stack oder Queue | Schneller als Stack-Klasse |
HashSet | Standard für Sets | O(1) für add/contains |
HashMap | Standard für Maps | O(1) für put/get |
TreeSet/TreeMap | Sortierte Collections | Automatische Sortierung |
🔴 LEGACY – Diese vermeiden:
| Klasse | Problem | Alternative |
|---|---|---|
Vector | Synchronized (langsam), veraltet | ArrayList + Collections.synchronizedList() |
Stack | Erbt von Vector, Design-Fehler | ArrayDeque |
Hashtable | Synchronized (langsam), kein null | HashMap oder ConcurrentHashMap |
Enumeration | Veraltet, weniger Methoden | Iterator |
Warum sind die Legacy-Klassen langsamer?
Sie sind synchronized – jede Operation ist thread-safe, ob du es brauchst oder nicht. Das kostet Performance. In 95% der Fälle brauchst du keine Thread-Sicherheit, und wenn doch, gibt es bessere Lösungen wie ConcurrentHashMap.
💬 Jamal: „Real talk: Wenn du in Production-Code Vector oder Stack siehst, ist das ein Zeichen dass der Code alt ist. Bei Refactoring würde ich die ersetzen.“
Warum brauche ich Collections statt Arrays?
💬 Nova fragt: „Elyndra, ehrlich – ich hab bisher immer Arrays benutzt. Warum soll ich das ändern?“
Gute Frage, Nova. Lass mich dir zeigen, warum:
Das Array-Problem:
// Array mit fester Größe String[] teilnehmer = new String[3]; teilnehmer[0] = "Anna"; teilnehmer[1] = "Ben"; teilnehmer[2] = "Clara"; // Jetzt will sich "David" anmelden... // Upps. Array ist voll. Was jetzt? // Option 1: Neues, größeres Array erstellen String[] neuesTeilnehmer = new String[4]; System.arraycopy(teilnehmer, 0, neuesTeilnehmer, 0, 3); neuesTeilnehmer[3] = "David"; teilnehmer = neuesTeilnehmer; // Das ist... mühsam. 😅
Die Collections-Lösung:
// ArrayList wächst automatisch
List<String> teilnehmer = new ArrayList<>();
teilnehmer.add("Anna");
teilnehmer.add("Ben");
teilnehmer.add("Clara");
teilnehmer.add("David"); // Einfach hinzufügen. Fertig.
// Jemand verlässt den Kurs?
teilnehmer.remove("Ben"); // Eine Zeile. Done.
Was macht dieser Code?
Die ArrayList kümmert sich intern um das Größenmanagement. Du fügst Elemente hinzu, entfernst sie – die Liste passt sich automatisch an.
Der Vergleich auf einen Blick:
| Eigenschaft | Array | ArrayList |
|---|---|---|
| Größe | Fest bei Erstellung | Dynamisch |
| Element hinzufügen | Manuelles Kopieren | add() |
| Element entfernen | Manuelles Verschieben | remove() |
| Suchen | Schleife schreiben | contains(), indexOf() |
| Primitive Typen | ✅ Direkt | ❌ Nur Wrapper (Integer, Double…) |
💬 Nova: „Oh Mann! Warum hat mir das niemand früher gesagt?!“
Keine Sorge – jeder hat mal mit Arrays angefangen. Das ist völlig normal.
Das Collection Interface
Bevor wir tiefer in Listen eintauchen, ein kurzer Blick auf die gemeinsame Basis: das Collection Interface.
public interface Collection<E> extends Iterable<E> {
// Größe und Status
int size();
boolean isEmpty();
// Elemente hinzufügen/entfernen
boolean add(E element);
boolean remove(Object o);
void clear();
// Suchen
boolean contains(Object o);
// Konvertierung
Object[] toArray();
<T> T[] toArray(T[] a);
// Bulk-Operationen
boolean addAll(Collection<? extends E> c);
boolean removeAll(Collection<?> c);
boolean retainAll(Collection<?> c);
}
Was macht dieser Code?
Das Collection Interface definiert die Grundoperationen, die ALLE Collections können – egal ob List, Set oder Queue. Das <E> ist ein Generic – ein Platzhalter für den Typ, den du speichern willst. Dazu mehr an Tag 3!
Wichtig zu verstehen:
Das E steht für „Element“ – eine Konvention. Wenn du List<String> schreibst, wird E zu String. Bei List<Integer> wird E zu Integer. So weiß der Compiler, welche Typen erlaubt sind.
Das List Interface
List erweitert Collection um Index-basierte Operationen:
public interface List<E> extends Collection<E> {
// Index-Zugriff
E get(int index);
E set(int index, E element);
// Position-basiertes Einfügen/Entfernen
void add(int index, E element);
E remove(int index);
// Suche mit Index
int indexOf(Object o);
int lastIndexOf(Object o);
// Sublist
List<E> subList(int fromIndex, int toIndex);
// Spezielle Iteratoren
ListIterator<E> listIterator();
ListIterator<E> listIterator(int index);
}
Das Besondere an Listen:
Listen sind geordnet – die Reihenfolge, in der du Elemente hinzufügst, bleibt erhalten. Und du kannst über den Index auf jedes Element zugreifen, genau wie bei Arrays.
Deine erste ArrayList
Zeit für Praxis! Die ArrayList ist die am häufigsten verwendete List-Implementierung.
import java.util.ArrayList;
import java.util.List;
public class MeineErsteListe {
public static void main(String[] args) {
// Liste erstellen
List<String> einkaufsliste = new ArrayList<>();
// Elemente hinzufügen
einkaufsliste.add("Milch");
einkaufsliste.add("Brot");
einkaufsliste.add("Käse");
einkaufsliste.add("Äpfel");
// Liste ausgeben
System.out.println("Einkaufsliste: " + einkaufsliste);
// Ausgabe: Einkaufsliste: [Milch, Brot, Käse, Äpfel]
// Größe abfragen
System.out.println("Anzahl: " + einkaufsliste.size());
// Ausgabe: Anzahl: 4
// Element an Position 1 abrufen (0-basiert!)
System.out.println("Zweites Element: " + einkaufsliste.get(1));
// Ausgabe: Zweites Element: Brot
// Prüfen ob Element vorhanden
System.out.println("Haben wir Milch? " + einkaufsliste.contains("Milch"));
// Ausgabe: Haben wir Milch? true
// Element entfernen
einkaufsliste.remove("Brot");
System.out.println("Nach Entfernen: " + einkaufsliste);
// Ausgabe: Nach Entfernen: [Milch, Käse, Äpfel]
// Element an bestimmter Position einfügen
einkaufsliste.add(1, "Butter");
System.out.println("Mit Butter: " + einkaufsliste);
// Ausgabe: Mit Butter: [Milch, Butter, Käse, Äpfel]
}
}
Was macht dieser Code?
Wir erstellen eine ArrayList für Strings und führen die wichtigsten Operationen durch: hinzufügen, abrufen, prüfen, entfernen, einfügen.
Wie funktioniert das im Detail?
Die Deklaration: List<String> einkaufsliste = new ArrayList<>();
Hier passiert etwas Wichtiges: Wir deklarieren die Variable als List<String> (Interface), erstellen aber ein ArrayList-Objekt (Implementierung).
Warum nicht einfach ArrayList<String> einkaufsliste = new ArrayList<>();?
Das ist einer der wichtigsten OOP-Grundsätze: Programmiere gegen das Interface, nicht die Implementierung.
💬 Nova fragt: „Häh?! Was bringt mir das? Funktioniert doch beides?!“
Stell dir vor, du hast eine Methode:
// ❌ Mit konkreter Klasse - unflexibel
public void verarbeite(ArrayList<String> daten) { ... }
// ✅ Mit Interface - flexibel
public void verarbeite(List<String> daten) { ... }
Die zweite Variante akzeptiert jede List-Implementierung: ArrayList, LinkedList, oder sogar deine eigene Custom-List. Du bindest dich nicht an eine konkrete Implementierung.
Praktisches Beispiel:
List<String> namen = new ArrayList<>(); // Heute: ArrayList // Später merkst du: LinkedList wäre besser für dein Problem List<String> namen = new LinkedList<>(); // Nur EINE Zeile ändern! // Der ganze REST deines Codes funktioniert weiter! // Alle Methoden die List<String> erwarten, funktionieren mit beiden.
💬 Jamal: „In Production hab ich das oft erlebt: Projekt startet mit ArrayList, dann merkt jemand dass eine Queue besser wäre. Wer gegen das Interface programmiert hat, ändert eine Zeile. Wer
ArrayListüberall hingeschrieben hat, refactored zwei Tage.“
Das ist Polymorphismus in Aktion – ein Kernkonzept aus der OOP.
Der Diamond-Operator:
<>Seit Java 7 musst du den Typ rechts nicht wiederholen. Der Compiler leitet ihn von links ab. Statt
new ArrayList<String>()reichtnew ArrayList<>().Die Methoden:
add(element)– Fügt am Ende hinzuadd(index, element)– Fügt an Position ein, verschiebt Rest nach rechtsget(index)– Gibt Element an Position zurück (0-basiert!)remove(element)– Entfernt erstes Vorkommenremove(index)– Entfernt Element an Positioncontains(element)– Prüft ob vorhandensize()– Gibt Anzahl zurück
In der Praxis bedeutet das:
Du kannst Listen fast wie Arrays verwenden – aber mit eingebauter Flexibilität. Kein manuelles Größenmanagement mehr!
💡 Neu hier? Was ist ein Interface?
Ein Interface ist wie ein Vertrag. Es sagt: „Jede Klasse, die mich implementiert, MUSS diese Methoden haben.“
Listist das Interface,ArrayListundLinkedListsind Implementierungen, die diesen Vertrag erfüllen.Beispiel: Wenn eine Methode
List<String>erwartet, kannst du JEDE List-Implementierung übergeben.
ArrayList vs. LinkedList – Wann was?
Java bietet zwei Haupt-Implementierungen von List. Die Wahl hat echte Performance-Auswirkungen.
ArrayList – Das dynamische Array
List<String> arrayList = new ArrayList<>();
Wie funktioniert ArrayList intern?
Eine ArrayList ist im Kern ein Array, das sich automatisch vergrößert. Wenn du Elemente hinzufügst und das interne Array voll ist, erstellt Java ein neues, größeres Array und kopiert alle Elemente um.

Abbildung 2: ArrayList vergrößert ihr internes Array automatisch
Stärken:
- ✅ Schneller Zugriff per Index:
get(5)ist sofort (O(1)) - ✅ Speichereffizient (keine Overhead pro Element)
- ✅ Cache-freundlich (zusammenhängender Speicher)
Schwächen:
- ❌ Einfügen/Entfernen in der Mitte: Alle folgenden Elemente müssen verschoben werden
- ❌ Vergrößerung: Komplettes Kopieren nötig
LinkedList – Die verkettete Liste
List<String> linkedList = new LinkedList<>();
Wie funktioniert LinkedList intern?
Eine LinkedList besteht aus Knoten (Nodes), die über Referenzen verbunden sind. Jeder Knoten kennt seinen Vorgänger und Nachfolger.

Abbildung 3: LinkedList als doppelt verkettete Knotenstruktur
Stärken:
- ✅ Schnelles Einfügen/Entfernen am Anfang/Ende: O(1)
- ✅ Kein Kopieren bei Größenänderung
Schwächen:
- ❌ Langsamer Index-Zugriff:
get(500)muss 500 Knoten durchlaufen (O(n)) - ❌ Mehr Speicher pro Element (Referenzen zu prev/next)
Die Entscheidungshilfe
| Szenario | Beste Wahl | Warum? |
|---|---|---|
| Häufiger Zugriff per Index | ArrayList | O(1) vs O(n) |
| Hauptsächlich am Ende hinzufügen | ArrayList | Amortisiert O(1), cache-freundlich |
| Häufiges Einfügen/Entfernen am Anfang | LinkedList | O(1) vs O(n) |
| Häufiges Einfügen/Entfernen in der Mitte | Kommt drauf an | Bei bekannter Position: LinkedList |
| Speicher ist kritisch | ArrayList | Weniger Overhead |
| Als Queue/Deque nutzen | LinkedList | Implementiert Deque Interface |
💬 Jamal: „Real talk: In 90% der Fälle ist
ArrayListdie richtige Wahl. Ich hab in drei Jahren Production-Code vielleicht fünfmal bewusstLinkedListgewählt. Meistens als Queue.“
Immutable Lists mit List.of()
Seit Java 9 gibt es eine elegante Möglichkeit, unveränderliche Listen zu erstellen:
// Immutable List erstellen
List<String> wochentage = List.of("Montag", "Dienstag", "Mittwoch",
"Donnerstag", "Freitag");
// Das geht:
String tag = wochentage.get(2); // "Mittwoch"
int anzahl = wochentage.size(); // 5
// Das geht NICHT:
wochentage.add("Samstag"); // UnsupportedOperationException!
wochentage.remove("Montag"); // UnsupportedOperationException!
wochentage.set(0, "Monday"); // UnsupportedOperationException!
Was macht dieser Code?
List.of() erstellt eine unveränderliche (immutable) Liste. Nach der Erstellung kannst du sie nur lesen, nicht mehr verändern.
Warum ist das nützlich?
- Thread-Sicherheit: Immutable Objects können bedenkenlos zwischen Threads geteilt werden
- Sicherheit: Keine versehentlichen Änderungen möglich
- Klarheit: Der Code zeigt: „Diese Liste ist fix“
Wichtig zu verstehen:
List.of() erlaubt auch keine null-Werte! Das ist anders als bei ArrayList:
// Das funktioniert:
List<String> mitNull = new ArrayList<>();
mitNull.add(null); // OK
// Das nicht:
List<String> ohneNull = List.of("A", null, "B"); // NullPointerException!
💬 Nova: „Moment, Moment… warum sollte ich absichtlich eine Liste wollen, die ich nicht ändern kann?“
Gute Frage! Stell dir vor, du hast eine Konfiguration oder Konstanten. Du willst sicher sein, dass niemand – auch nicht versehentlich – diese Werte ändert. Oder du gibst eine Liste aus einer Methode zurück und willst garantieren, dass der Aufrufer sie nicht manipuliert.
Durch Listen iterieren
Es gibt mehrere Wege, durch eine Liste zu gehen. Jeder hat seinen Platz.
1. For-Each Loop (empfohlen für die meisten Fälle)
List<String> namen = List.of("Anna", "Ben", "Clara");
for (String name : namen) {
System.out.println("Hallo, " + name + "!");
}
Vorteile: Sauber, lesbar, wenig Fehleranfällig
Nachteile: Kein Zugriff auf den Index, keine Modifikation während Iteration
2. Klassische For-Schleife (wenn du den Index brauchst)
List<String> namen = new ArrayList<>(List.of("Anna", "Ben", "Clara"));
for (int i = 0; i < namen.size(); i++) {
System.out.println((i + 1) + ". " + namen.get(i));
}
// Ausgabe:
// 1. Anna
// 2. Ben
// 3. Clara
Vorteile: Zugriff auf Index
Nachteile: Mehr Code, leicht Off-by-One-Fehler
3. Iterator (wenn du während der Iteration entfernen willst)
List<String> namen = new ArrayList<>(List.of("Anna", "Ben", "Clara", "Bob"));
Iterator<String> iterator = namen.iterator();
while (iterator.hasNext()) {
String name = iterator.next();
if (name.startsWith("B")) {
iterator.remove(); // Sicher entfernen während Iteration!
}
}
System.out.println(namen); // [Anna, Clara]
Vorteile: Sicheres Entfernen während Iteration
Nachteile: Etwas mehr Boilerplate
⚠️ Wichtig: Wenn du mit for-each iterierst und gleichzeitig
list.remove()aufrufst, bekommst du eineConcurrentModificationException! Der Iterator ist der sichere Weg.
4. ListIterator (für bidirektionale Iteration)
List<String> namen = new ArrayList<>(List.of("Anna", "Ben", "Clara"));
ListIterator<String> listIterator = namen.listIterator(namen.size()); // Start am Ende
// Rückwärts iterieren
while (listIterator.hasPrevious()) {
System.out.println(listIterator.previous());
}
// Ausgabe:
// Clara
// Ben
// Anna
Vorteile: Vorwärts UND rückwärts, Zugriff auf Index, Ersetzen möglich
Nachteile: Selten nötig
Wrapper-Klassen – Primitive in Collections
Collections können keine primitiven Typen speichern – nur Objekte. Für int, double, boolean usw. gibt es Wrapper-Klassen:
| Primitiv | Wrapper-Klasse |
|---|---|
int | Integer |
double | Double |
boolean | Boolean |
char | Character |
long | Long |
float | Float |
byte | Byte |
short | Short |
Autoboxing und Unboxing:
Java konvertiert automatisch zwischen primitiven Typen und ihren Wrappern:
List<Integer> zahlen = new ArrayList<>();
// Autoboxing: int → Integer
zahlen.add(42); // Java macht daraus: zahlen.add(Integer.valueOf(42))
zahlen.add(17);
// Unboxing: Integer → int
int erste = zahlen.get(0); // Java macht daraus: zahlen.get(0).intValue()
System.out.println("Summe: " + (zahlen.get(0) + zahlen.get(1))); // 59
Was passiert hier wirklich?
Wenn du zahlen.add(42) schreibst, ruft Java im Hintergrund Integer.valueOf(42) auf, um den primitiven int in ein Integer-Objekt zu verpacken. Beim Auslesen passiert das Gegenteil.
Vorsicht bei null:
List<Integer> zahlen = new ArrayList<>(); zahlen.add(null); // Erlaubt! int wert = zahlen.get(0); // NullPointerException! // Weil: null.intValue() geht nicht
💬 Jamal: „Das ist ein klassischer Bug in Production. Immer auf null prüfen, wenn du mit Wrapper-Klassen arbeitest. Oder gleich
OptionalIntverwenden – dazu später mehr.“
🟡 PROFESSIONALS
💡 Du hast die Grundlagen drauf? Hier geht’s um Best Practices und Praxis-Tipps.
Best Practices
1. Programmiere gegen das Interface
// ✅ GUT: Interface als Typ List<String> namen = new ArrayList<>(); // ❌ VERMEIDEN: Konkrete Klasse als Typ ArrayList<String> namen = new ArrayList<>();
Warum? Wenn du später auf LinkedList wechseln willst, musst du nur die rechte Seite ändern. Alle Methoden, die List<String> erwarten, funktionieren weiterhin.
2. Initiale Kapazität bei bekannter Größe
// Wenn du ungefähr weißt, wie viele Elemente kommen: List<String> grosseListe = new ArrayList<>(10000); // Statt: List<String> grosseListe = new ArrayList<>(); // Startet mit Kapazität 10
Warum? Jede Vergrößerung der ArrayList bedeutet: neues Array erstellen, alles kopieren. Bei 10.000 Elementen mit Standardkapazität passiert das mehrmals.
💬 Jamal: „In einem Projekt hatten wir einen Import-Job, der 50.000 Datensätze lud. Einfach
new ArrayList<>(50000)stattnew ArrayList<>()hat die Laufzeit um 30% reduziert. Lowkey ein Game-Changer.“
3. Verwende List.of() für Konstanten
// ✅ GUT: Immutable für Konstanten
private static final List<String> ERLAUBTE_STATUS =
List.of("AKTIV", "PAUSIERT", "BEENDET");
// ❌ VERMEIDEN: Mutable für Konstanten
private static final List<String> ERLAUBTE_STATUS =
new ArrayList<>(Arrays.asList("AKTIV", "PAUSIERT", "BEENDET"));
// Jemand könnte versehentlich ERLAUBTE_STATUS.add("GELOESCHT") aufrufen!
4. Defensive Kopien erstellen
Wenn du eine Liste aus einer Methode zurückgibst und nicht willst, dass der Aufrufer deine interne Liste verändert:
public class Kurs {
private List<String> teilnehmer = new ArrayList<>();
// ✅ GUT: Defensive Kopie
public List<String> getTeilnehmer() {
return List.copyOf(teilnehmer); // Immutable Kopie
}
// ❌ GEFÄHRLICH: Direkte Referenz
public List<String> getTeilnehmerUnsafe() {
return teilnehmer; // Aufrufer kann DEINE Liste ändern!
}
}
Häufige Patterns
Pattern 1: Liste filtern
List<Integer> zahlen = new ArrayList<>(List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10));
// Alle geraden Zahlen entfernen (mit Iterator)
Iterator<Integer> iter = zahlen.iterator();
while (iter.hasNext()) {
if (iter.next() % 2 == 0) {
iter.remove();
}
}
// zahlen: [1, 3, 5, 7, 9]
// Oder eleganter mit removeIf (seit Java 8):
zahlen.removeIf(n -> n % 2 == 0);
Pattern 2: Liste transformieren
List<String> namen = List.of("anna", "ben", "clara");
// Neue Liste mit transformierten Werten
List<String> grossNamen = new ArrayList<>();
for (String name : namen) {
grossNamen.add(name.toUpperCase());
}
// grossNamen: [ANNA, BEN, CLARA]
// Mit Streams (ab Tag 6 mehr dazu):
List<String> grossNamen = namen.stream()
.map(String::toUpperCase)
.toList();
Pattern 3: Null-sichere Operationen
public void verarbeite(List<String> eingabe) {
// ✅ Null-Check am Anfang
if (eingabe == null || eingabe.isEmpty()) {
return;
}
// Jetzt sicher arbeiten
for (String item : eingabe) {
// ...
}
}
// Oder mit Objects.requireNonNullElse (Java 9+):
List<String> sicher = Objects.requireNonNullElse(eingabe, List.of());
Stolperfallen vermeiden
Stolperfalle 1: ConcurrentModificationException
// ❌ DAS GEHT SCHIEF:
List<String> namen = new ArrayList<>(List.of("Anna", "Ben", "Bob", "Clara"));
for (String name : namen) {
if (name.startsWith("B")) {
namen.remove(name); // BOOM! ConcurrentModificationException
}
}
// ✅ RICHTIG: Mit Iterator
Iterator<String> iter = namen.iterator();
while (iter.hasNext()) {
if (iter.next().startsWith("B")) {
iter.remove(); // OK!
}
}
// ✅ ODER: Mit removeIf
namen.removeIf(name -> name.startsWith("B"));
Stolperfalle 2: remove() mit Index vs. Objekt bei Integer-Listen
List<Integer> zahlen = new ArrayList<>(List.of(10, 20, 30, 40)); zahlen.remove(1); // Entfernt Element an INDEX 1 → [10, 30, 40] zahlen.remove((Integer) 30); // Entfernt das OBJEKT 30 → [10, 40]
Bei Integer-Listen ist remove(1) mehrdeutig! Java wählt die spezifischere Methode – und das ist remove(int index). Wenn du ein Integer-Objekt entfernen willst, musst du casten.
Stolperfalle 3: subList() erstellt keine Kopie
List<String> original = new ArrayList<>(List.of("A", "B", "C", "D", "E"));
List<String> sub = original.subList(1, 4); // [B, C, D]
sub.set(0, "X"); // Ändert auch original!
System.out.println(original); // [A, X, C, D, E]
original.add("F"); // Ändert Struktur
sub.get(0); // ConcurrentModificationException!
subList() erstellt eine View auf die Original-Liste, keine Kopie. Änderungen in der Subliste wirken sich auf das Original aus – und strukturelle Änderungen am Original machen die Subliste kaputt.
🔵 BONUS
💡 Für Neugierige und Profis: Performance-Details, Edge Cases und fortgeschrittene Patterns.
Big-O Analyse
| Operation | ArrayList | LinkedList |
|---|---|---|
get(index) | O(1) | O(n) |
add(element) (am Ende) | O(1) amortisiert | O(1) |
add(index, element) (am Anfang) | O(n) | O(1) |
add(index, element) (in der Mitte) | O(n) | O(n)* |
remove(index) | O(n) | O(n)* |
contains(element) | O(n) | O(n) |
iterator.remove() | O(n) | O(1) |
*LinkedList ist O(1) für die eigentliche Operation, aber O(n) um die Position zu finden.
Was bedeutet das praktisch?
Bei einer Liste mit 1 Million Elementen:
ArrayList.get(500000)→ Sofort (ein Speicherzugriff)LinkedList.get(500000)→ 500.000 Sprünge durch Referenzen
💬 Jamal: „Ich hab mal einen Junior-Code reviewed, der in einer Schleife
linkedList.get(i)aufgerufen hat. Bei 10.000 Elementen: 5 Sekunden Laufzeit. Nach Umstellung auf ArrayList: 2 Millisekunden. Performance matters.“
Memory Layout
Die internen Strukturen von ArrayList und LinkedList unterscheiden sich fundamental:
ArrayList: Zusammenhängender Speicherbereich – das macht sie cache-freundlich. Moderne CPUs laden Daten in Cache-Lines (typisch 64 Bytes). Bei ArrayList liegen die Referenzen nebeneinander, mehrere passen in eine Cache-Line.
LinkedList: Jeder Node liegt irgendwo im Heap. Beim Durchlaufen springt die CPU ständig zu verschiedenen Speicheradressen – das verursacht Cache-Misses und kostet Performance.
💡 Siehe Abbildungen 2 und 3 oben für die visuelle Darstellung.
Wann LinkedList tatsächlich schneller ist
Ein echtes Beispiel, wo LinkedList gewinnt:
// Szenario: Playlist, bei der oft am Anfang eingefügt wird
public class Playlist {
private final Deque<String> songs = new LinkedList<>();
// O(1) bei LinkedList, O(n) bei ArrayList
public void alsNaechstesSpielen(String song) {
songs.addFirst(song);
}
// O(1) bei LinkedList
public String naechsterSong() {
return songs.pollFirst();
}
}
Wenn du als Queue/Deque nutzt (FIFO/LIFO-Operationen am Anfang/Ende), ist LinkedList die bessere Wahl.
List.copyOf() vs. Konstruktor-Kopie
List<String> original = new ArrayList<>(List.of("A", "B", "C"));
// Methode 1: Konstruktor-Kopie (mutable)
List<String> kopie1 = new ArrayList<>(original);
kopie1.add("D"); // OK
// Methode 2: List.copyOf() (immutable, Java 10+)
List<String> kopie2 = List.copyOf(original);
kopie2.add("D"); // UnsupportedOperationException!
// Wichtig: Beide sind UNABHÄNGIG vom Original
original.add("X");
System.out.println(kopie1); // [A, B, C, D] - unverändert
System.out.println(kopie2); // [A, B, C] - unverändert
Edge Case: equals() und hashCode() bei eigenen Objekten
Wenn du eigene Objekte in Listen speicherst und contains(), remove() oder indexOf() nutzen willst, MUSST du equals() überschreiben:
public class Produkt {
private String id;
private String name;
// Konstruktor, Getter...
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Produkt produkt = (Produkt) o;
return Objects.equals(id, produkt.id);
}
@Override
public int hashCode() {
return Objects.hash(id);
}
}
Ohne equals() vergleicht Java nur Referenzen – zwei Produkte mit gleicher ID wären dann „ungleich“, wenn sie verschiedene Objekte sind.
💬 Real Talk: Die Sache mit ArrayList und LinkedList
Büro von Java Fleet, 15:30 Uhr. Nova sitzt mit gerunzelter Stirn vor ihrem Monitor.
Nova: „Elyndra, ich hab da was gelesen… LinkedList soll für Einfügen schneller sein. Soll ich die jetzt überall nutzen?“
Elyndra: „Lass mich raten – Stack Overflow?“
Nova: „…Ja. 😅“
Jamal: (dreht sich von seinem Schreibtisch um) „Die Antwort ist wahrscheinlich von 2008. Damals war das anders.“
Elyndra: „Jamal hat recht. Die Theorie sagt: LinkedList ist O(1) für Einfügen am Anfang. Aber die Praxis…“
Jamal: „In der Praxis verlierst du bei LinkedList durch Cache-Misses. Moderne CPUs sind optimiert für zusammenhängende Speicherbereiche. ArrayList liegt zusammenhängend im Speicher. LinkedList ist überall verstreut.“
Nova: „Okay, aber wenn ich ständig am Anfang einfüge?“
Elyndra: „Dann kann LinkedList schneller sein. Aber frag dich: Wie oft passiert das wirklich? In den meisten Anwendungen fügst du am Ende hinzu und iterierst durch.“
Jamal: „Real talk: Ich hab in drei Jahren Production-Code vielleicht fünfmal bewusst LinkedList gewählt. Und das war als Queue, nicht als List.“
Nova: „Also ArrayList als Default?“
Elyndra: „Genau. ArrayList als Default, LinkedList wenn du einen spezifischen Grund hast – und den kannst du benennen.“
Nova: „Das ist ja eigentlich simpler als ich dachte!“
Jamal: (lächelt kurz) „Die einfachen Antworten sind meistens die richtigen.“
💡 Praxis-Tipps
Für Einsteiger 🌱
- Starte immer mit ArrayList: In 90% der Fälle ist sie die richtige Wahl
- Nutze das Interface als Typ:
List<String>stattArrayList<String> - Vergiss
size()nicht: Listen sind 0-indiziert, das letzte Element ist beisize() - 1
Für den Alltag 🌿
- Setze initiale Kapazität: Bei bekannter ungefährer Größe
new ArrayList<>(expectedSize) - Nutze
removeIf()statt Iterator: Sauberer und weniger fehleranfällig - Defensive Kopien: Wenn du interne Listen nach außen gibst, nutze
List.copyOf()
Für Profis 🌳
- Benchmark vor Optimierung: Vermutungen über Performance sind oft falsch
- Consider
Arrays.asList()für feste Listen: Erstellt eine Fixed-Size-Liste backed by Array - Für Thread-Sicherheit:
Collections.synchronizedList()oderCopyOnWriteArrayList
🛠️ Tools & Ressourcen
Für Einsteiger 🌱
| Tool | Warum? | Link |
|---|---|---|
| JShell | Schnell Collections ausprobieren | In JDK enthalten |
| Oracle Java Tutorial | Offizielle Grundlagen | docs.oracle.com |
Für den Alltag 🌿
| Tool | Warum? | Link |
|---|---|---|
| IntelliJ IDEA | Zeigt Performance-Warnungen | jetbrains.com |
| Baeldung | Praktische Java-Artikel | baeldung.com |
Für Profis 🌳
| Tool | Warum? | Link |
|---|---|---|
| JMH | Micro-Benchmarking | openjdk.org/projects/code-tools/jmh |
| Java Almanac | API-Vergleich zwischen Versionen | javaalmanac.io |
❓ FAQ (Häufige Fragen)
Frage 1: Muss ich immer den Typ angeben bei List<String>?
Antwort: Ja, seit Java 5 sind Generics Standard. Ohne Typangabe (List statt List<String>) bekommst du eine „raw type“ Warnung. Der Compiler kann dann keine Typfehler erkennen. Immer den Typ angeben!
Frage 2: Was ist der Unterschied zwischen List.of() und Arrays.asList()?
Antwort: List.of() (Java 9+) erstellt eine komplett immutable Liste – keine Änderungen möglich, kein null erlaubt. Arrays.asList() erstellt eine Liste mit fester Größe – set() funktioniert, aber add() und remove() nicht. Außerdem ist sie backed by the array, Änderungen wirken sich aus.
Frage 3: Kann ich primitive Typen in Listen speichern?
Antwort: Nicht direkt. Du brauchst Wrapper-Klassen: List<Integer> statt List<int>. Java macht Autoboxing, aber sei vorsichtig mit null-Werten – die können bei Unboxing zu NullPointerException führen.
Frage 4: Wie sortiere ich eine Liste?
Antwort: Mit Collections.sort(list) oder seit Java 8 mit list.sort(comparator). Für natürliche Sortierung: list.sort(Comparator.naturalOrder()). Für eigene Sortierung: list.sort(Comparator.comparing(Person::getName)).
Frage 5: ArrayList oder Vector?
Antwort: ArrayList. Vector ist legacy und synchronized, was in den meisten Fällen unnötig und langsamer ist. Wenn du Thread-Sicherheit brauchst, nutze Collections.synchronizedList() oder CopyOnWriteArrayList.
Frage 6: Gibt es Listen mit primitiven Typen ohne Wrapper-Overhead?
Antwort: Nicht in der Standard-Bibliothek. Aber Libraries wie Eclipse Collections oder Trove bieten primitive Collections. Für die meisten Anwendungen ist der Wrapper-Overhead aber vernachlässigbar.
Frage 7: Wer entscheidet eigentlich, welche List-Implementierung bei Java Fleet verwendet wird? 🤔
Antwort: Gute Frage! Meistens Elyndra oder Jamal – die haben ein Auge für Performance. Aber manchmal, ganz selten, taucht ein mysteriöser Commit auf mit einem Kommentar, der alles auf den Punkt bringt. Die Signatur? Nur ein „B.“ …Niemand weiß, wer das ist. Wenn du mehr über die Geheimnisse von Java Fleet erfahren willst, such mal nach „behind the code“ oder „in my feels“. 😉
Frage 8: Warum soll ich List als Typ nutzen statt ArrayList?
Antwort: Das Liskov Substitution Principle! Wenn du gegen das Interface programmierst, kannst du die Implementierung austauschen, ohne den Rest des Codes zu ändern. Methoden, die List<String> akzeptieren, funktionieren mit JEDER List-Implementierung.
🎁 Cheat Sheet
🟢 Basics (Zum Nachschlagen)
// Liste erstellen
List<String> liste = new ArrayList<>();
List<String> immutable = List.of("A", "B", "C");
// Grundoperationen
liste.add("Element"); // Am Ende hinzufügen
liste.add(0, "Erstes"); // An Position einfügen
liste.get(0); // Element abrufen
liste.set(0, "Neu"); // Element ersetzen
liste.remove("Element"); // Nach Wert entfernen
liste.remove(0); // Nach Index entfernen
liste.size(); // Größe
liste.isEmpty(); // Leer?
liste.contains("X"); // Enthält?
liste.indexOf("X"); // Position finden
liste.clear(); // Alles löschen
🟡 Patterns (Für den Alltag)
// Iteration
for (String s : liste) { } // For-each
liste.forEach(s -> System.out.println(s)); // Lambda
liste.forEach(System.out::println); // Method Reference
// Filtern
liste.removeIf(s -> s.startsWith("X"));
// Sortieren
liste.sort(Comparator.naturalOrder());
liste.sort(Comparator.comparing(String::length));
// Kopieren
List<String> kopie = new ArrayList<>(liste); // Mutable Kopie
List<String> kopie = List.copyOf(liste); // Immutable Kopie
🔵 Advanced (Für Profis)
// Initiale Kapazität List<String> gross = new ArrayList<>(10000); // Thread-sicher List<String> sync = Collections.synchronizedList(new ArrayList<>()); // Sublist (Vorsicht: View, keine Kopie!) List<String> teil = liste.subList(1, 4); // In Array konvertieren String[] array = liste.toArray(new String[0]); String[] array = liste.toArray(String[]::new); // Java 11+
🎨 Challenge für dich!
Wähle dein Level:
🟢 Level 1 – Einsteiger
- [ ] Erstelle eine ToDo-Liste mit ArrayList
- [ ] Implementiere Hinzufügen, Anzeigen, Entfernen
- [ ] Speichere die erledigten Tasks in einer zweiten Liste
Geschätzte Zeit: 15-30 Minuten
🟡 Level 2 – Fortgeschritten
- [ ] Erweitere die ToDo-Liste um Prioritäten (HIGH, MEDIUM, LOW)
- [ ] Sortiere die Liste nach Priorität
- [ ] Implementiere eine Undo-Funktion mit einer zweiten Liste
Geschätzte Zeit: 30-60 Minuten
🔵 Level 3 – Profi
- [ ] Implementiere eine eigene SimpleList-Klasse (ohne ArrayList zu nutzen)
- [ ] Unterstütze: add, get, remove, size, contains
- [ ] Schreibe Unit-Tests für alle Methoden
- [ ] Bonus: Implementiere das Iterable-Interface
Geschätzte Zeit: 1-2 Stunden
Teile dein Ergebnis! 🎉
Java Erweiterte Techniken - Tag 1
Listen – Upgrade von Arrays
📦 Downloads
Alle Code-Beispiele zum Herunterladen:
| Projekt | Für wen? | Download |
|---|---|---|
| tag01-listen-starter.zip | 🟢 Einsteiger | ⬇️ Download |
| tag01-listen-complete.zip | 🟡 Musterlösung | ⬇️ Download |
Quick Start:
# 1. ZIP entpacken unzip tag01-listen-starter.zip # 2. In NetBeans öffnen # Datei → Projekt öffnen → Ordner auswählen # 3. Main-Klasse ausführen # Rechtsklick auf Main.java → Datei ausführen
🔗 Weiterführende Links
🇩🇪 Deutsch
| Ressource | Beschreibung |
|---|---|
| Rheinwerk OpenBook: Java SE 8 | Kostenloses Standardwerk, Kapitel Datenstrukturen |
| Javabeginners: Sammlungen | Einsteigerfreundliche Erklärungen |
| StudySmarter: Java Collections | Übersicht mit Lernkarten |
| IONOS: Java List | Methoden und Anwendungen erklärt |
🇬🇧 Englisch
| Ressource | Beschreibung | Level |
|---|---|---|
| Oracle Java Tutorials: Collections | Offizielle Dokumentation | 🟢 |
| W3Schools: Java ArrayList | Interaktive Beispiele | 🟢 |
| Programiz: Java LinkedList | Visualisierungen & Erklärungen | 🟢 |
| GeeksforGeeks: LinkedList | Detaillierte Code-Beispiele | 🟡 |
| Baeldung: Java List | Deep Dive für Profis | 🟡 |
🛠️ Tools & Extensions
| Tool | Beschreibung |
|---|---|
| JShell | REPL für schnelles Testen |
| IntelliJ IDEA Community | IDE mit Collection-Hints |
📧 Offizielle Dokumentation
💬 Geschafft! 🎉
Was du heute gelernt hast:
✅ Das Java Collections Framework und seine Struktur
✅ ArrayList und LinkedList – und wann du welche nutzt
✅ Immutable Lists mit List.of() erstellen
✅ Verschiedene Iterationsarten durch Listen
✅ Wrapper-Klassen und Autoboxing
✅ Best Practices für den Produktiveinsatz
Egal ob du heute zum ersten Mal von Collections gehört hast oder dein Wissen vertieft hast – du hast etwas Neues gelernt. Das zählt!
Fragen? Schreib uns:
- Elyndra: elyndra.valen@java-developer.online
- Nova: nova.trent@java-developer.online
- Jamal: jamal.hassan@java-developer.online
Nächster Teil: Sets & Maps – Keine Duplikate, schnelles Finden 🚀
Keep learning, keep growing! 💚
Tags: #Java #Collections #ArrayList #LinkedList #JavaGrundlagen #Tutorial
📚 Das könnte dich auch interessieren
© 2025 Java Fleet Systems Consulting | java-developer.online

