Einleitung: Das Problem, das Generics lösen
Stellen Sie sich vor, Sie schreiben eine Methode zum Sortieren einer Liste von Zahlen. Sie schreiben sie für Integer, und eine Woche später müssen Sie eine Liste von String sortieren. Sie müssen eine neue Methode erstellen oder Object verwenden und ständig Typumwandlungen durchführen. Das ist umständlich, unsicher und führt zu Laufzeitfehlern.
Genau dieses Problem lösen Generics in Java. Sie ermöglichen die Erstellung von Klassen, Schnittstellen und Methoden, die mit verschiedenen Datentypen arbeiten, dabei aber eine strenge Typisierung zur Kompilierzeit beibehalten. Ohne Generics wäre modernes Java weitaus weniger komfortabel und sicher.
In diesem Artikel werden wir untersuchen, warum Generics benötigt werden, wie sie funktionieren und, am wichtigsten, wie man sie anhand realer Beispiele anwendet.
1. Grundlagen von Generics: Was sie sind und wie sie funktionieren
1.1. Das Problem der "rohen" Typen (Raw Types)
Vor dem Erscheinen von Java 5 (2004) verwendeten Entwickler Collections ohne Typangabe:
List list = new ArrayList();list.add("Hallo");list.add(123);// Beim Abrufen muss man sich merken, welcher Typ vorliegtString text = (String) list.get(0); // OKInteger num = (Integer) list.get(1); // OK// Aber bei einem Fehler — ClassCastException zur LaufzeitString error = (String) list.get(1); // Fehler!Dieser Code ist gefährlich: Der Fehler wird erst beim Ausführen des Programms entdeckt.
1.2. Wie Generics funktionieren
Generics ermöglichen es, den Datentyp, mit dem eine Klasse oder Methode arbeiten soll, direkt in spitzen Klammern <T> anzugeben. Der Compiler überprüft die Typen zur Kompilierzeit und fügt automatisch die korrekten Umwandlungen ein.
Grundlegende Syntax:
// Deklaration einer generischen Klassepublic class Box<T> { private T content; public void setContent(T content) { this.content = content; } public T getContent() { return content; }}
// VerwendungBox<String> stringBox = new Box<>(); // <> — Diamond-Operator (Java 7+)stringBox.setContent("Hallo");String text = stringBox.getContent(); // Ohne Umwandlung!
Box<Integer> intBox = new Box<>();intBox.setContent(42);int number = intBox.getContent(); // Ohne Umwandlung!
// stringBox.setContent(123); // Kompilierfehler!1.3. Vorteile in der Praxis
- Typsicherheit: Fehler werden zur Kompilierzeit erkannt, nicht zur Laufzeit.
- Wegfall expliziter Umwandlungen: Der Code wird sauberer und lesbarer.
- Wiederverwendbarkeit von Code: Eine Klasse/Methode arbeitet mit jedem Typ.
2. Generics in der Praxis: Klassen, Methoden und Schnittstellen
2.1. Generische Klassen
Der häufigste Fall sind Collections. Sie können aber auch Ihre eigenen generischen Klassen erstellen:
public class Pair<K, V> { private K key; private V value; public Pair(K key, V value) { this.key = key; this.value = value; } public K getKey() { return key; } public V getValue() { return value; }}
// VerwendungPair<String, Integer> pair = new Pair<>("Alter", 30);String key = pair.getKey();Integer value = pair.getValue();2.2. Generische Methoden
Eine Methode kann ihre eigenen Typparameter haben, die nicht mit der Klasse zusammenhängen:
public class Utils { // Generische Methode zum Erstellen einer Liste aus einem Array public static <T> List<T> arrayToList(T[] array) { List<T> list = new ArrayList<>(); for (T element : array) { list.add(element); } return list; }}
// VerwendungString[] names = {"Anna", "Boris", "Viktor"};List<String> nameList = Utils.arrayToList(names);