1. Das Problem „roher“ Collections (Raw Types)
Kurz ein Blick in die Geschichte. Vor Java 5 waren alle Collections „allesfressend“. Sie speicherten Objekte vom Typ Object, und der Compiler kontrollierte nicht, was genau Sie hineinlegten. Möchten Sie einen String ablegen? Bitte. Eine Zahl? Warum nicht. Eine Katze? Geht auch.
// Beispiel "roher" Collections (Raw Types), Java bis Version 5
List list = new ArrayList();
list.add("Hallo");
list.add(42);
list.add(new Object());
Das Problem zeigte sich beim „Herausnehmen“ und Verwenden des Werts:
String s = (String) list.get(0); // OK, das ist ein String
String s2 = (String) list.get(1); // BUMM! ClassCastException
Der Compiler schweigt, und zur Laufzeit erhalten Sie eine ClassCastException. Das ist wie eine Schachtel mit der Aufschrift „Äpfel“, in der eine Tasse, eine Banane und ein Igel liegen.
Warum ist das schlecht?
- Fehler zeigen sich erst zur Laufzeit.
- Typen werden vermischt: Objekte müssen manuell auf den benötigten Typ gecastet werden.
- Der Code ist weniger lesbar und riskanter.
Lösung – Generics (generische Typen)
Generics (generische Typen) sind ein Mechanismus, mit dem man Klassen, Interfaces und Methoden mit Typparametern erstellen kann. Das heißt, Sie sagen der Collection: „Speichere nur Strings“, und der Compiler achtet strikt darauf.
List<String> words = new ArrayList<>();
words.add("Hallo");
words.add("Welt");
// words.add(42); // Kompilierfehler! In List<String> kann man keinen int hinzufügen
Jetzt lässt der Compiler nicht zu, dass in die Liste etwas anderes als Strings gelegt wird. Der Fehler wird vor dem Start des Programms abgefangen.
Hauptidee von Generics:
Typsicherheit für Collections (und mehr) gewährleisten, damit Fehler beim Kompilieren statt zur Laufzeit gefunden werden.
2. Syntax von Generics: wie das im Code aussieht
Typangabe in spitzen Klammern
Wenn Sie eine Collection erstellen, geben Sie den Elementtyp in <> an:
List<String> names = new ArrayList<>();
names.add("Alice");
names.add("Bob");
// names.add(123); // Fehler: Eine Zahl kann nicht zu einer String-Liste hinzugefügt werden
String first = names.get(0); // Kein Cast nötig!
Klassiker:
- List<String> — Liste von Strings
- List<Integer> — Liste ganzer Zahlen
- Set<Double> — Menge von Gleitkommazahlen
- Map<String, Integer> — Schlüssel String, Wert Integer
Warum nicht einfach nur List schreiben?
Kann man, aber man verliert alle Vorteile von Generics, und der Compiler warnt:
List list = new ArrayList(); // raw type — nicht empfohlen!
list.add("Hello");
list.add(7.5);
String s = (String) list.get(1); // Hallo, ClassCastException!
Moderner Java-Code verwendet immer Generics.
Diamond-Operator <>
Seit Java 7 muss man den Typ rechts nicht mehr angeben, wenn er aus dem Kontext klar ist:
List<String> list = new ArrayList<>(); // Der Compiler erkennt hier selbst, dass es sich um <String> handelt
3. Generics für verschiedene Collections
Beispiele für List, Set, Map
List<Integer> numbers = new ArrayList<>();
numbers.add(10);
numbers.add(20);
Set<String> uniqueNames = new HashSet<>();
uniqueNames.add("Alice");
uniqueNames.add("Bob");
Map<String, Integer> ages = new HashMap<>();
ages.put("Alice", 23);
ages.put("Bob", 31);
Beispiel mit eigener Klasse
class Student {
String name;
int age;
// ...
}
List<Student> students = new ArrayList<>();
students.add(new Student());
4. Nützliche Details
Vorteile von Generics
Typsicherheit. Der Compiler stellt sicher, dass in die Collection nur Elemente des gewünschten Typs gelangen.
Kein Cast mehr nötig. Früher: String s = (String) list.get(0);. Jetzt: String s = list.get(0);.
Der Code ist lesbarer und zuverlässiger. Weniger Überraschungen zur Laufzeit.
Einschränkungen von Generics
Primitive Typen können nicht verwendet werden. Generics arbeiten nur mit Objekten, nicht mit Primitiven (int, double, boolean). Verwenden Sie Wrapper-Klassen: Integer, Double, Boolean.
List<Integer> numbers = new ArrayList<>();
numbers.add(10); // int wird automatisch in Integer umgewandelt (Autoboxing)
Kurz zur Typenlöschung (type erasure)
In Java sind Generics über den Mechanismus der Typenlöschung implementiert: Nach der Kompilierung wird die Information über Typparameter gelöscht, und zur Laufzeit weiß die JVM nicht, dass es List<String> war und nicht einfach List. Das geschieht aus Gründen der Abwärtskompatibilität.
Folge: Man kann den Typparameter nicht mit instanceof zusammen mit einem konkreten Typargument prüfen.
List<String> list = new ArrayList<>();
// if (list instanceof List<String>) { ... } // Kompilierfehler!
Der Versuch, ein Element eines anderen Typs hinzuzufügen – ein Kompilierfehler
List<String> words = new ArrayList<>();
words.add("Hello");
// words.add(123); // Kompilierfehler: incompatible types: int cannot be converted to String
Map<String, Integer> map = new HashMap<>();
map.put("Katze", 5);
// map.put(3, "Elefant"); // Fehler: Der Schlüssel muss String sein, der Wert — Integer
Und das ist großartig: Fehler werden bereits beim Kompilieren gefunden.
Nicht nur Collections
Generics kann man in eigenen Klassen und Methoden verwenden. Zum Beispiel eine universelle „Box“:
class Box<T> {
private T value;
public void set(T value) { this.value = value; }
public T get() { return value; }
}
Box<String> stringBox = new Box<>();
stringBox.set("Hallo");
System.out.println(stringBox.get());
Box<Integer> intBox = new Box<>();
intBox.set(42);
System.out.println(intBox.get());
Bei Collections sind Generics Standard, aber Sie begegnen ihnen auch an anderen Stellen, z. B. in Stream API und Optional.
5. Typische Fehler im Umgang mit Generics
Fehler Nr. 1: Verwendung von „rohen“ Collections. Eine Zuweisung wie List list = new ArrayList(); nimmt Ihnen die Typsicherheit. Geben Sie immer Typparameter an, z. B. List<String>.
Fehler Nr. 2: Versuch, Primitive zu verwenden. Man kann nicht List<int> schreiben, verwenden Sie List<Integer>.
Fehler Nr. 3: Manuelles Casten beim Lesen aus der Collection. Wenn Sie Generics verwenden, ist ein Cast wie (String) list.get(i) nicht nötig. Wenn Sie ihn brauchen, haben Sie irgendwo die Typen verletzt.
Fehler Nr. 4: Erwartung, dass Typparameter zur Laufzeit verfügbar sind. Wegen der Typenlöschung kann man sie nicht mit instanceof à la List<String> prüfen.
Fehler Nr. 5: Verschiedene Typen in einer Collection mischen. Wenn List<String> deklariert ist, fügen Sie keinen Integer hinzu – der Compiler lässt das nicht zu, und das ist gut so.
GO TO FULL VERSION