Warum Generics?
- Typsicherheit zur Kompilierungszeit (weniger
ClassCastException). - Code-Wiederverwendung für verschiedene Typen.
- API-Dokumentation durch
Map<String, Integer>ist verständlicher alsMap.
Basissyntax: <T>, <E>, <K, V>
Klasse/Interface mit Typparameter
public class Box<T> {
private T value;
public Box(T value) { this.value = value; }
public T get() { return value; }
public void set(T value) { this.value = value; }
}
Box<String> sb = new Box<>("hi");
String s = sb.get(); // sicher, ohne CastMehrere Parameter
public class Pair<K, V> {
private final K key; private final V value;
public Pair(K key, V value) { this.key = key; this.value = value; }
public K key() { return key; } public V value() { return value; }
}Generische Methoden
public static <T> T first(List<T> list) {
return list.isEmpty() ? null : list.get(0);
}Beschränkungen (Bounds)
public class NumberBox<T extends Number> { /* ... */ }
// mehrere Beschränkungen:
public static <T extends Number & Comparable<T>> T max(T a, T b) {
return a.compareTo(b) >= 0 ? a : b;
}Wildcards: ?, ? extends, ? super + Prinzip PECS
Invarianz: Der Schlüssel zum Verständnis
List<Integer> ist nicht weder List<Number>, noch ein Subtyp, noch ein Supertyp.
Arrays sind covariant (Number[] arr = new Integer[10]), Generics sind invariant.
Wildcard ?
List<?> ist eine Liste eines unbekannten Typs: man kann es als Object lesen, aber man kann nichts hinzufügen (außer null).
Obere Grenze: ? extends T (“Producer”)
void printNums(List<? extends Number> src) {
// kann als Number gelesen werden
Number n = src.get(0);
// src.add(42); // nicht möglich – der genaue Typ ist unbekannt
}Untere Grenze: ? super T (“Consumer”)
void addIntegers(List<? super Integer> dst) {
dst.add(1); // kann Integer (und seine Subtypen) hinzufügen
// Integer x = dst.get(0); // gibt Object zurück, unsichere Lektüre
}Das Goldene PECS-Regel
- Producer Extends: Wenn man liest (Quelle), verwendet man
? extends. - Consumer Super: Wenn man schreibt (Empfänger), verwendet man
? super.
Klassisches Beispiel: Collections.copy
public static <T> void copy(List<? super T> dest, List<? extends T> src) { ... }Quelle – extends, Empfänger – super.
Häufige Fragen und Fallen
Warum List<Object> ≠ List<String>?
Invarianz: Andernfalls könnte man Integer in List<String> über einen List<Object>-Link einfügen.
Verwenden Sie List<?>, wenn man eine “beliebige Liste nur zum Lesen” benötigt.
Was unterscheidet List<? extends Number> von List<Number>?
- In
List<Number>kann manInteger,Long,Doubleusw. hinzufügen. - In
List<? extends Number>kann man nicht hinzufügen (der genaue Typ ist unbekannt), aber man kann ihn sicher als Number lesen.
Wozu ist List<? super Integer> nützlich?
Das ist ein “Eimer” zum Schreiben von Objekten vom Typ Integer. Man kann von dort nur als Object lesen.
Wildcard-Erfassung (Wildcard Capture)
Manchmal benötigt man einen “Helfer”, um mit ? zu arbeiten:
static void swapFirst(List<?> list) { swapHelper(list); }
private static <T> void swapHelper(List<T> list) {
if (list.size() > 1) {
T tmp = list.get(0);
list.set(0, list.get(1));
list.set(1, tmp);
}
}Type Inference (Typableitung)
“Diamant” (diamond) ab Java 7+
Map<String, List<Integer>> m = new HashMap<>();Ableitung in generischen Methoden
var x = List.of(1, 2, 3); // List<Integer>
var y = first(List.of("a")); // StringManchmal explizit <T> bei der Aufruf verwenden: MyClass.<Long>first(...).
Type Erasure (Typentupfung)
Was ist das: Typparameter existieren nur zur Kompilierungszeit.
Die JVM sieht “rohe” Typen: List<T> → List. Daher Einschränkungen:
Konsequenzen der Typentupfung
- Man kann keinen Array eines parametrisierten Typs erstellen:
List<String>[] arr = new List<String>[10]; // Fehler- Man kann keine
new T()undT.classverwenden:
T t = new T(); // Fehler
Class<T> c = T.class; // FehlerUmgehung: Class<T> in den Konstruktor/Methode übergeben:
class Factory<T> {
private final Class<T> type;
Factory(Class<T> type) { this.type = type; }
T create() throws Exception { return type.getDeclaredConstructor().newInstance(); }
}- Man kann Methoden nicht überschreiben, die sich nur in den Typparametern unterscheiden:
void foo(List<String> x) {}
void foo(List<Integer> y) {} // gleiche Erradikation → Konfliktinstanceofnur mit Wildcard oder rohen Typen:
if (obj instanceof List<?>) { /* ok */ }
// if (obj instanceof List<String>) // Kompilierungsfehler- Bridge-Methoden: Der Compiler generiert sie, um den Polymorphismus bei der Typentupfung zu erhalten (oft im Bytecode/Debug sichtbar).
- Reifiable vs non-reifiable:
- Reifiable (erhalten Infos zur Laufzeit):
List<?>,List, Array von primitiven Typen. - Non-reifiable:
List<String>,Map<Integer, String>– der Typparameter wird entfernt.
Type Token / Typ speichern
Benutzen “Typträger”:
abstract class TypeRef<T> {
final Type type = ((ParameterizedType) getClass()
.getGenericSuperclass()).getActualTypeArguments()[0];
}
TypeRef<List<String>> ref = new TypeRef<>() {};
System.out.println(ref.type); // java.util.List<java.lang.String>Generics und Arrays/Varargs
- Arrays sind covariant und werden zur Laufzeit überprüft; Generics sind invariant und werden zur Kompilierungszeit überprüft.
- Varargs mit Generics führen zu einer Warnung
heap pollution.
Markieren Sie die Methode@SafeVarargs(nurstatic/final/private) und mischen Sie keine internen Details:
@SafeVarargs
public static <T> List<T> asList(T... items) { return Arrays.asList(items); }Praktische Signaturen für Interviews
- Maximaler Comparator
public static <T extends Comparable<? super T>> T max(List<? extends T> list) {
if (list.isEmpty()) return null;
T best = list.get(0);
for (T t : list) if (t.compareTo(best) > 0) best = t;
return best;
}- Kopieren aus Quelle in Empfänger (PECS)
public static <T> void copyAll(List<? super T> dest, List<? extends T> src) {
for (T t : src) dest.add(t);
}- Allgemeiner Repository im Spring-Stil
public interface Repository<T extends BaseEntity, ID> {
T save(T entity);
Optional<T> findById(ID id);
}Typische Fehler
- ❌ Rohe Typen (
List list) → verlieren Sicherheit, es kommt zuClassCastException. - ❌ Rückgabe von
List<? extends T>aus einer API-Methode. BesserList<T>zurückgeben; Wildcards in den Argumenten, nicht in den Rückgabetypen. - ❌ Missbrauch von
? extendsdort, wo man schreiben sollte. Denken Sie an PECS. - ❌ Versuchen, Methoden zu überschreiben, die sich nur in den Typparametern unterscheiden (Typentupfung wird es brechen).
- ❌
List<Object>stattList<?>für “beliebige Liste nur zum Lesen”.
Best Practices
- Halten Sie sich an PECS:
extends– lesen,super– schreiben. - Verwenden Sie Wildcards in den Parametern für öffentliche APIs, nicht in den Rückabetypen.
- Vermeiden Sie rohe Typen; falls nötig, isolieren Sie sie und kommentieren Sie
@SuppressWarnings("unchecked")mit einem Kommentar. - Verwenden Sie sinvolle Namen für Typparameter:
T,E,K,V,ID,R,U. - Für Factorys/Reflexion verwenden Sie
Class<T>oder den Typenträger (type token), nichtnew T().
Mini-Worterbuch der Unterschiede (zum Merken)
List<?>– kann alsObjectgelesen, nicht geschrieben.List<? extends T>– kann alsTgelesen, nicht geschrieben.List<? super T>– kann schreibenT, lesen alsObject.- Generics sind invariant; Arrays sind covariant.
- Typentupfung: kein
new T(),instanceof List<String>und Überschreibungen “nur über Generics”.