Generics
Kurz: Typparameter in spitzen Klammern (<T>), mit denen eine Klasse oder Methode für beliebige Typen wiederverwendbar wird, ohne die Typsicherheit aufzugeben.
Genauer: Ohne Generics müsste eine Collection mit dem allgemeinen Typ Object arbeiten und Werte beim Herausholen manuell zurückcasten — fehleranfällig, da Typfehler erst zur Laufzeit auffallen. Mit List<String> erzwingt der Compiler dagegen schon beim Kompilieren, dass nur String-Objekte hineinkommen.
class Box<T> {
private T inhalt;
void set(T inhalt) { this.inhalt = inhalt; }
T get() { return inhalt; }
}Im Detail
class Box<T> {
private T inhalt;
void set(T inhalt) { this.inhalt = inhalt; }
T get() { return inhalt; }
}
Box<String> textBox = new Box<>();
textBox.set("Hallo");
String wert = textBox.get(); // kein Cast nötig - Compiler kennt den Typ
// Generische Methode mit eigenem Typparameter
static <T> T ersteElement(List<T> liste) {
return liste.get(0);
}
// Bounded Type Parameter - T muss Number oder eine Unterklasse sein
static <T extends Number> double summe(List<T> zahlen) {
double summe = 0;
for (T zahl : zahlen) summe += zahl.doubleValue();
return summe;
}Generics existieren NUR zur Compile-Zeit (“Type Erasure”) — zur Laufzeit weiß die JVM tatsächlich nicht mehr, dass eine List<String> speziell Strings enthält, sie sieht nur eine ganz normale List. Das erklärt, warum man z. B. new T[10] (ein Array eines generischen Typs erzeugen) in Java nicht direkt schreiben kann, und warum instanceof List<String> nicht funktioniert (nur instanceof List ist erlaubt). Der Vorteil trotz dieser Einschränkung: der Compiler fängt Typfehler schon beim Schreiben des Codes ab, bevor das Programm überhaupt läuft — ohne Generics wären solche Fehler oft erst als ClassCastException mitten in der Ausführung sichtbar. <T extends Number> (Bounded Type Parameter) schränkt ein, WELCHE Typen als T eingesetzt werden dürfen, und erlaubt gleichzeitig, Methoden von Number (wie doubleValue()) direkt auf T-Werten aufzurufen.
Wildcards: ? extends und ? super
Neben konkreten Typparametern (<T>) unterstützt Java auch Wildcards für Methoden, die eine Collection lesen ODER schreiben sollen, ohne den exakten Typ zu kennen:
// ? extends Number - kann von JEDER Number-Unterklasse-Liste lesen (Integer, Double, ...)
static double summeVon(List<? extends Number> liste) {
double summe = 0;
for (Number n : liste) summe += n.doubleValue();
return summe;
}
summeVon(List.of(1, 2, 3)); // List<Integer> funktioniert
summeVon(List.of(1.5, 2.5)); // List<Double> funktioniert auchDie Faustregel dafür heißt “PECS” (Producer Extends, Consumer Super): Wenn eine Methode nur aus der Collection LIEST (produziert Werte für den Aufrufer), nutzt man ? extends T; wenn sie nur in die Collection SCHREIBT (konsumiert Werte vom Aufrufer), nutzt man ? super T.
Warum keine Arrays generischer Typen
Ein häufiger Compiler-Fehler beim Experimentieren mit Generics ist der Versuch, ein Array eines generischen Typs zu erzeugen:
class Container<T> {
// T[] elemente = new T[10]; // Compiler-Fehler: "generic array creation"
Object[] elemente = new Object[10]; // Workaround: als Object[] anlegen, dann intern casten
}Das liegt direkt an der Type Erasure: Arrays in Java “wissen” zur Laufzeit, welchen Typ sie enthalten (für Typprüfungen beim Einfügen), Generics dagegen nicht mehr — diese Inkompatibilität verbietet der Compiler von vornherein, statt ein möglicherweise fehlerhaftes Array zur Laufzeit zuzulassen.
Siehe auch: Wrapper Classes, Collections, List