Generics in Java: warum sie nötig sind und wie man sie verwendet
Generics (Generics) sind eine der Schlüsselfunktionen der Sprache Java, die in der Version J2SE 5.0 eingeführt wurde. Sie ermöglichen die Erstellung von Klassen, Schnittstellen und Methoden, die mit verschiedenen Datentypen arbeiten, während sie eine strenge Typisierung zur Kompilierungszeit beibehalten. In diesem Artikel werden wir untersuchen, warum Generics benötigt werden, wie man sie richtig verwendet und welche Fehler sie vermeiden helfen.
Warum werden Generics in Java benötigt?
Vor dem Aufkommen von Generics verwendeten Programmierer Typumwandlungen (Casting) und die Arbeit mit Object. Dies führte zu zwei Hauptproblemen:
- Fehlende Typsicherheit — einer Sammlung konnte ein Objekt beliebigen Typs hinzugefügt werden, und der Fehler zeigte sich erst zur Laufzeit.
- Notwendigkeit expliziter Umwandlungen — beim Abrufen eines Elements aus einer Sammlung war ein Cast erforderlich, was den Code überlud.
// Ohne Generics (Java vor 5)List list = new ArrayList();list.add("Zeichenkette");list.add(123); // Kein Fehler zur KompilierungszeitString s = (String) list.get(0); // Muss explizit umgewandelt werden// Mit GenericsList<String> list = new ArrayList<>();list.add("Zeichenkette");// list.add(123); // Kompilierungsfehler!String s = list.get(0); // Keine Umwandlung nötigDie Hauptvorteile von Generics:
- Typsicherheit — Typfehler werden zur Kompilierungszeit erkannt.
- Beseitigung expliziter Umwandlungen — der Code wird sauberer und lesbarer.
- Wiederverwendung von Code — eine Implementierung funktioniert mit verschiedenen Typen.
Erstellung generischer Klassen und Methoden
Generische Klasse
Um eine Klasse zu erstellen, die mit einem beliebigen Typ arbeitet, genügt es, den Typparameter in spitzen Klammern nach dem Klassennamen anzugeben:
public class Box<T> { private T content;
public void setContent(T content) { this.content = content; }
public T getContent() { return content; }
public static void main(String[] args) { Box<String> stringBox = new Box<>(); stringBox.setContent("Hallo, Java!"); System.out.println(stringBox.getContent());
Box<Integer> intBox = new Box<>(); intBox.setContent(42); System.out.println(intBox.getContent()); }}In diesem Beispiel ist T der Typparameter. Beim Erstellen eines Objekts Box<String> werden alle Vorkommen von T durch String ersetzt.
Generische Methode
Methoden können ebenfalls parametrisiert werden. Der Typparameter wird vor dem Rückgabetyp angegeben:
public class Util { public static <T> void printArray(T[] array) { for (T element : array) { System.out.print(element + " "); } System.out.println(); }
public static void main(String[] args) { Integer[] intArray = {1, 2, 3, 4, 5}; String[] strArray = {"A", "B", "C"};
printArray(intArray); // 1 2 3 4 5 printArray(strArray); // A B C }}Beachten Sie: Der Typparameter <T> steht vor void. Dies ist für generische Methoden zwingend erforderlich.
Eingeschränkte Typen (Bounded Type Parameters)
Manchmal muss eingeschränkt werden, welche Typen als Parameter verwendet werden können. Dazu werden die Schlüsselwörter extends und super verwendet.
Obere Grenze (extends)
Schränkt den Typ nach oben ein — es kann nur die angegebene Klasse selbst oder ihre Unterklassen verwendet werden:
public class Calculator<T extends Number> { private T value;
public Calculator(T value) { this.value = value; }
public double square() { return va