- 1 Section
- 10 Lessons
- unbegrenzt
- Java Fortgeschritten10
- 1.1OOP in Java: Klassen, Kapselung, Konstruktoren
- 1.2Vererbung in Java
- 1.3Polymorphismus und Methodenüberschreibung
- 1.4Interfaces und abstrakte Klassen
- 1.5Collections: ArrayList, HashMap, LinkedList
- 1.6Generics und Typsicherheit
- 1.7Streams und Lambda-Ausdrücke
- 1.8Dateioperationen: IO und NIO
- 1.9Unit-Tests in Java: JUnit 5
- 1.10Praxisprojekt: Anwendung mit OOP und Tests
Collections: ArrayList, HashMap, LinkedList
In K40a hast du normale Arrays kennengelernt: int[] zahlen = new int[10];. Arrays haben aber harte Grenzen – ihre Größe ist fix, sie können nichts dynamisch wachsen oder schrumpfen, sie haben keine eingebauten Suchfunktionen.
Die Java Collections sind die Antwort: dynamische, mächtige Datenstrukturen aus der Standardbibliothek. Diese Lektion zeigt dir die wichtigsten – ArrayList (dynamische Liste), LinkedList (verkettete Liste), HashSet (Menge ohne Duplikate), HashMap (Schlüssel-Wert-Speicher) – und wann du welche nehmen solltest. Plus: das Collections-Framework als Ganzes, damit du dich in der Java-Welt zurechtfindest.
1) Warum nicht einfach Arrays?
Arrays haben in Java drei Nachteile:
- Feste Größe: einmal angelegt, lässt sich die Länge nicht ändern
- Keine Methoden: kein
add(),remove(),contains(), keinindexOf()– alles selbst schreiben - Niedriges Abstraktions-Niveau: man arbeitet mit Indizes statt mit Inhalten
Collections lösen das. Sie sind dynamisch, kommen mit reichhaltigen Methoden und folgen einheitlichen Schnittstellen, sodass dein Code wiederverwendbar bleibt.
2) Das Collections-Framework
Java strukturiert seine Datenstrukturen in einer Hierarchie aus Interfaces und konkreten Klassen:
Collection<E>
Map<K,V>
Map ist nicht Teil von Collection – sie ist ein eigenes Interface, weil sie Schlüssel-Wert-Paare statt einzelner Elemente speichert.3) ArrayList – die dynamische Liste
Die meistgenutzte Collection in Java. Intern ein Array, das automatisch wächst, wenn nötig:
import java.util.ArrayList; import java.util.List; List<String> namen = new ArrayList<>(); namen.add("Anna"); namen.add("Ben"); namen.add("Carla"); System.out.println(namen.get(1)); // Ben System.out.println(namen.size()); // 3 System.out.println(namen.contains("Ben")); // true namen.remove("Anna"); namen.set(0, "Bernd"); System.out.println(namen); // [Bernd, Carla]
Beachte das Muster: deklariert als List<String>, instanziiert als ArrayList<>. Das ist die „Programmiere gegen das Interface"-Regel aus L4. Wenn du später auf eine andere List-Implementierung wechseln willst, musst du nur die rechte Seite ändern.
4) Wie ArrayList intern funktioniert
Intern hält eine ArrayList ein gewöhnliches Array. Wenn der Platz ausgeht, legt sie ein größeres Array an (oft 1,5× so groß) und kopiert alle Elemente um:
get(i)) ist O(1) – ein einfacher Array-Zugriff. add am Ende ist meist auch O(1), außer wenn das interne Array vergrößert werden muss. Einfügen oder Löschen mittendrin ist O(n), weil alle nachfolgenden Elemente verschoben werden.5) LinkedList – die verkettete Liste
Eine LinkedList speichert Elemente als verkettete Knoten, jeder Knoten kennt seinen Nachfolger (und Vorgänger). Anders organisiert, andere Performance-Eigenschaften:
import java.util.LinkedList; List<String> warteschlange = new LinkedList<>(); warteschlange.add("Anna"); warteschlange.add("Ben"); ((LinkedList<String>) warteschlange).addFirst("Chefin"); System.out.println(warteschlange); // [Chefin, Anna, Ben]
6) ArrayList vs. LinkedList
Faustregel:
- ArrayList ist Standard. Schneller Zugriff per Index, kompakter Speicher
- LinkedList nur, wenn du häufig am Anfang einfügst oder eine echte Queue/Deque brauchst
- In der Praxis ist ArrayList in 95 % der Fälle die richtige Wahl
7) HashSet – Mengen ohne Duplikate
Ein Set speichert Elemente einmalig – Duplikate werden ignoriert. HashSet ist die häufigste Implementierung:
import java.util.HashSet; import java.util.Set; Set<String> markenSet = new HashSet<>(); markenSet.add("BMW"); markenSet.add("VW"); markenSet.add("BMW"); // ignoriert – schon enthalten System.out.println(markenSet.size()); // 2 System.out.println(markenSet.contains("BMW")); // true
Typische Anwendungen:
- Duplikate entfernen:
new HashSet<>(meineListe)liefert eine Sammlung ohne Doppler - Schnelle Mitgliedschaftsprüfung:
containsin O(1) – ideal für „kommt dieser Wert vor?" - Sortiertes Set:
TreeSethält Elemente sortiert (etwas langsamer, dafür immer in Reihenfolge)
equals() und hashCode() sinnvoll überschreiben (siehe L1). Ohne korrekte Implementierung gelten zwei inhaltlich gleiche Objekte als verschieden – Duplikate kommen ins Set.
8) HashMap – Schlüssel-Wert-Speicher
Eine HashMap ordnet Schlüsseln Werte zu – wie ein Wörterbuch:
import java.util.HashMap; import java.util.Map; Map<String, Integer> alter = new HashMap<>(); alter.put("Anna", 28); alter.put("Ben", 35); alter.put("Carla", 22); System.out.println(alter.get("Ben")); // 35 System.out.println(alter.containsKey("Dirk")); // false // Iteration über Schlüssel-Wert-Paare for (Map.Entry<String, Integer> e : alter.entrySet()) { System.out.println(e.getKey() + ": " + e.getValue()); }
9) Wie HashMap intern funktioniert
Eine HashMap hat intern ein Array von „Buckets". Aus dem Hash-Code des Schlüssels wird ein Bucket-Index berechnet. Im Bucket landen Schlüssel-Wert-Paare; bei Kollisionen werden mehrere Paare in einer Liste oder einem Baum gespeichert:
equals() verglichen. Im Schnitt landet jeder Schlüssel allein, daher O(1) für get und put.Typische Anwendungen einer HashMap:
- Lookups: Benutzername → Benutzerobjekt, Produktcode → Produkt
- Zählen: Wort → Häufigkeit
- Caches: Eingabe → vorberechnetes Ergebnis
- Konfigurationen: Einstellungsname → Wert
10) Reihenfolge der Elemente
Wichtig zu wissen, welche Implementierung welche Reihenfolge garantiert:
| Klasse | Reihenfolge |
|---|---|
ArrayList, LinkedList | Einfüge-Reihenfolge |
HashSet, HashMap | keine garantierte Reihenfolge |
LinkedHashSet, LinkedHashMap | Einfüge-Reihenfolge |
TreeSet, TreeMap | natürliche oder eigene Sortierung |
Wenn die Reihenfolge dir wichtig ist, wähle entsprechend. Eine HashMap kann zwei Iterations-Durchläufe sogar in unterschiedlicher Reihenfolge liefern – verlasse dich nie darauf.
11) Iteration über Collections
Drei gleichwertige Wege, durch eine Liste zu gehen:
List<String> namen = List.of("Anna", "Ben", "Carla"); // 1) for-each (einfach, modern, kein Index) for (String n : namen) { System.out.println(n); } // 2) klassisch mit Index for (int i = 0; i < namen.size(); i++) { System.out.println(i + ": " + namen.get(i)); } // 3) Lambda + forEach (siehe L7) namen.forEach(System.out::println);
for-each ist meist die beste Wahl: einfach, lesbar, funktioniert mit jeder Collection. Den klassischen Index-Loop brauchst du nur, wenn du wirklich den Index benötigst.
12) Unveränderliche Collections
Manchmal will man eine Liste haben, die niemand mehr ändern darf – etwa für Konstanten oder Rückgabewerte. Java bietet zwei Wege:
// List.of(...) – seit Java 9, unveränderlich List<String> konst = List.of("Anna", "Ben"); konst.add("X"); // wirft UnsupportedOperationException // Collections.unmodifiableList – Wrapper um existierende Liste List<String> basis = new ArrayList<>(); basis.add("X"); List<String> ro = Collections.unmodifiableList(basis);
Unveränderliche Collections sind thread-safe (mehrere Threads können sie gleichzeitig lesen) und schließen viele Bug-Klassen aus. In neuen Codes ist List.of(...) die bevorzugte Form für kleine, konstante Listen.
13) Hilfsklasse Collections
Die Klasse java.util.Collections (Plural) bietet statische Hilfsmethoden:
List<Integer> zahlen = new ArrayList<>(List.of(3, 1, 4, 1, 5, 9)); Collections.sort(zahlen); // [1, 1, 3, 4, 5, 9] Collections.reverse(zahlen); // [9, 5, 4, 3, 1, 1] Collections.shuffle(zahlen); // zufällige Reihenfolge int max = Collections.max(zahlen); // 9 int freq = Collections.frequency(zahlen, 1); // 2
Nicht verwechseln: Collection (Singular) ist das Interface, Collections (Plural) ist die Hilfsklasse. Auch verwirrend: Arrays bietet ähnliche statische Methoden für klassische Arrays.
14) Generics und Type-Safety
Die spitzen Klammern <String> heißen Generics. Sie machen Collections typsicher – die DB kennt den Typ ihrer Elemente und der Compiler verhindert falsche Zuweisungen:
List<String> namen = new ArrayList<>(); namen.add("Anna"); namen.add(42); // COMPILE-FEHLER
Ohne Generics (vor Java 5) konnte man alles in eine Liste werfen und musste beim Lesen casten – fehleranfällig. Heute sind Generics Standard. Tiefer geht's in L6 (Generics).
15) Häufige Fehler
- Falsche Collection-Wahl:
containsauf ArrayList mit Millionen Einträgen ist langsam, bei HashSet schnell - HashSet ohne equals/hashCode: Duplikate werden nicht erkannt
- HashMap.get-Ergebnis ohne null-Check: bei fehlendem Schlüssel kommt
nullzurück. MitgetOrDefault(key, default)sicherer - ConcurrentModificationException: während einer for-Schleife eine Collection ändern – verwende stattdessen einen Iterator oder eine Kopie
- Falsche Initialisierung mit primitiven Typen:
List<int>geht nicht – mussList<Integer>sein (Auto-Boxing) - List.of immutable falsch verstanden: ein
addwirft eine Exception. Wer ändern will, brauchtnew ArrayList<>(List.of(...))
List, Set und Map, oder nach passenden Use-Cases („Welche Datenstruktur für …?"). Merke: List = geordnet mit Duplikaten, Set = ohne Duplikate, Map = Schlüssel-Wert.
Zusammenfassung
Das Java Collections Framework stellt dynamische Datenstrukturen über eine einheitliche Schnittstelle bereit. Die wichtigsten Interfaces: List (geordnet, mit Duplikaten), Set (ohne Duplikate) und Map (Schlüssel → Wert). Häufigste Implementierungen: ArrayList (Standard-Liste, schneller Index-Zugriff), LinkedList (für häufiges Einfügen am Anfang), HashSet (schnelle Mitgliedschaftsprüfung), HashMap (schneller Schlüssel-Wert-Lookup). Daneben gibt es sortierte Varianten (TreeSet, TreeMap) und reihenfolge-erhaltene Hash-Varianten (LinkedHashSet, LinkedHashMap). Eigene Klassen, die in Sets/Maps gespeichert werden, müssen equals und hashCode korrekt überschreiben. Mit Generics (List<String>) macht der Compiler die Typsicherheit. Unveränderliche Listen erzeugst du mit List.of(...).
