/
/
·
·
/
/
·
·
  • Einführung
  • Die Idee in einem Satz
  • Warum überhaupt Generics?, der historische Schmerz
  • Eigene generische Klasse: Box<T>
  • Generische Methode
  • Bounded Type-Parameter: T extends Comparable<T>
  • Wildcards: <?>, <? extends X>, <? super X>
  • Type-Erasure: der Compiler-Trick
  • Klausur-Faustregeln
  • Häufige Stolpersteine
  • Klausur-Beispielaufgabe
ThemenProgrammiergrundlagenGenerics
Programmiergrundlagen·4Lerneinheiten·36min·Stand17.07.2026

Generics.

Generics, Type-Parameter

List<String>, Map<Integer, Student>, Optional<T>, diese spitzen Klammern sind Generics. Sie sagen dem Compiler: "Diese Liste enthält NUR Strings, kein Integer, kein Object." Damit fängt Java Tippfehler beim Kompilieren statt erst zur Laufzeit mit einer ClassCastException. Du lernst hier, wie du eigene generische Klassen und Methoden schreibst, was Bounded Type-Parameter (T extends Comparable<T>) bedeuten, wie du Wildcards (<?>, <? extends X>, <? super X>) liest und mit der PECS-Regel anwendest, und warum Type-Erasure dafür sorgt, dass <T> zur Laufzeit verschwindet. In der Klausur löst dir das genau diese Fragetypen: Raw Types erkennen, Wildcards lesen, PECS anwenden, Type-Erasure erklären und Compile-Fehler vorhersagen.

Klausur-Pflicht in 10/13 WInf-Prog-1-Klausuren, Type-Parameter, Wildcards, Type-Erasure sind Standard-Stoff.

Generics: Du parametrisierst eine Klasse oder Methode mit einem Typ, statt einen festen Typ hart zu kodieren. Box<T> ist eine Box für irgendeinen Typ T, der beim Anlegen festgelegt wird.

Vor Java 5 gab es keine Generics. Listen sahen so aus:

List liste = new ArrayList();
liste.add("Hallo");
liste.add(42);  // kein Fehler! Objekt = alles geht
String s = (String) liste.get(1);  // CRASH zur Laufzeit

Die Liste konnte alles speichern, weil intern Object. Beim Rausholen musstest du casten, und wenn du falsch lagst, ClassCastException zur Laufzeit. Bug findet dich, nicht du den Bug.

Mit Generics:

List<String> liste = new ArrayList<>();
liste.add("Hallo");
liste.add(42);  // ← Compile-Fehler! int passt nicht in List<String>
String s = liste.get(0);  // kein Cast nötig

Type-Safety zur Compile-Zeit. Der ganze Vorteil von Generics in einem Satz.

public class Box<T> {
    private T inhalt;

    public void setInhalt(T inhalt) {
        this.inhalt = inhalt;
    }

    public T getInhalt() {
        return inhalt;
    }
}

Nutzen:

Box<String> textBox = new Box<>();
textBox.setInhalt("Hallo");
String s = textBox.getInhalt();  // kein Cast

Box<Integer> zahlBox = new Box<>();
zahlBox.setInhalt(42);
// zahlBox.setInhalt("Text");  // Compile-Fehler!

Das T ist nur ein Platzhalter. Beim Anlegen (new Box<String>()) behandelt der Compiler Box<String> typsicher als Box für Strings: Argumente und Rückgaben werden gegen String geprüft. Im Bytecode wird T später durch Type-Erasure verarbeitet (dazu unten mehr), zur Laufzeit existiert der konkrete Typparameter nicht mehr. Konvention: ein Buchstabe (T für Type, E für Element, K/V für Key/Value).

Auch einzelne Methoden können generisch sein, unabhängig von der Klasse:

public class Util {
    public static <T> T erstes(List<T> liste) {
        return liste.get(0);
    }
}

String s = Util.<String>erstes(myStringList);  // explizit
String s2 = Util.erstes(myStringList);          // Java schließt T = String

Das <T> vor dem Rückgabetyp deklariert den Type-Parameter dieser Methode. Achte genau auf die Position, dasselbe T taucht in einer Signatur in zwei völlig verschiedenen Rollen auf:

        DEKLARATION          RÜCKGABETYP
            │                     │
            ▼                     ▼
  public static <T>      T      erstes(List<T> liste)
                ───      ─                    ─
                 │       │                    │
   "ab hier ist T   "diese Methode      "Element-Typ der
    ein Typ-Param"   liefert ein T"      Eingabe-Liste"

Das erste <T> (in spitzen Klammern) ist die Deklaration, sie führt den Namen T ein. Jedes weitere T (Rückgabetyp, Parameter) ist eine Verwendung dieses Namens. Ohne das vorangestellte <T> wüsste der Compiler nicht, dass T ein Type-Parameter und kein realer Klassenname ist.

Manchmal brauchst du eine Bedingung an T, z.B. "T muss vergleichbar sein":

public static <T extends Comparable<T>> T max(List<T> liste) {
    T grösstes = liste.get(0);
    for (T element : liste) {
        if (element.compareTo(grösstes) > 0) {
            grösstes = element;
        }
    }
    return grösstes;
}

Jetzt darf T nur etwas sein, das Comparable ist, und du darfst compareTo aufrufen.

extends heißt hier sowohl "erbt von" als auch "implementiert" (Interfaces). Java spart sich ein zweites Wort für Interfaces in Generics-Bounds.

Profi-Hinweis: Robuster ist in echtem Java-Code <T extends Comparable<? super T>>, weil dann auch Typen erlaubt sind, deren compareTo von einem Obertyp geerbt ist. Für Prog-1 reicht aber <T extends Comparable<T>>.

Wildcards braucht man bei Methoden-Parametern, wenn der konkrete Typ egal sein darf:

WildcardBedeutungBeispiel
List<?>Liste von irgendwas (read-only)universelle Ausgabe-Funktion
List<? extends Number>Liste von Number oder Subklassen (Integer, Double)nur lesen ("Producer")
List<? super Integer>Liste von Integer oder Oberklassen (Number, Object)nur schreiben ("Consumer")

PECS-Regel: Producer Extends, Consumer Super. Wenn die Liste dir Werte liefert, nutze extends. Wenn du Werte reinschreibst, nutze super.

       liefert dir Werte (Producer)          du schreibst Werte rein (Consumer)
            ──── get() ───►                          ◄─── add() ────
  List<? extends Number>  src                 dst  List<? super Integer>
  -> lesen erlaubt, add verboten              -> add erlaubt, get nur als Object

Eselsbrücke: aus einem extends-Producer holst du nur heraus (kommt raus = du liest), in einen super-Consumer steckst du nur hinein (geht rein = du schreibst).

Im fertigen Bytecode verschwindet das <T>! Java ersetzt T durch Object (oder bei Bounds durch die obere Schranke) und fügt Casts ein, wo nötig. List<String> und List<Integer> sind zur Laufzeit dieselbe Klasse.

Merksatz, was bleibt vs. was verschwindet: Der Klassen-Typ existiert zur Laufzeit weiter, String.class ist real und abfragbar. Der generische Typparameter verschwindet: List<String> und List<Integer> haben dieselbe Runtime-Klasse (ArrayList). Es verschwinden also nicht alle Typinformationen, nur die <...>-Parameter.

So sieht derselbe Code vor und nach der Erasure aus:

VORHER (dein Quellcode, Compile-Zeit)     NACHHER (Bytecode, Laufzeit-Sicht)
─────────────────────────────────────     ──────────────────────────────────
class Box<T> {                            class Box {
    T inhalt;                                 Object inhalt;        // T -> Object
    T getInhalt() { return inhalt; }          Object getInhalt() { return inhalt; }
}                                         }

Box<String> b = new Box<>();              Box b = new Box();
String s = b.getInhalt();                 String s = (String) b.getInhalt();  // Cast eingefügt

Der Compiler garantiert die Typsicherheit also VOR der Erasure und packt danach die nötigen Casts selbst rein. Konsequenzen:

List<String> l1 = new ArrayList<>();
List<Integer> l2 = new ArrayList<>();
l1.getClass() == l2.getClass();  // true! Beide geben ArrayList.class zurück (true, weil == hier Identity prüft, also dasselbe Class-Objekt im Speicher, nicht equals())

// und das hier ist KEIN Compile-Fehler:
if (obj instanceof List) { ... }  // erlaubt
// aber das hier IST ein Compile-Fehler:
if (obj instanceof List<String>) { ... }  // nicht erlaubt

Du kannst auch keine Generic-Arrays anlegen:

T[] array = new T[10];  // Compile-Fehler
List<String>[] arr = new List<String>[5];  // Compile-Fehler (parameterisierter Typ)

Was hingegen erlaubt ist, zum Kontrast: ein Array mit unbounded Wildcard, weil List<?> ein reifizierbarer Typ ist (keine konkrete Typ-Information, die durch Erasure verloren ginge):

List<?>[] arr = new List<?>[5];  // erlaubt

Brauchst du wirklich ein generisches Array, gibt es zwei Wege. Der saubere: einfach List<T> statt Array verwenden, dann hast du das Problem nie. Der unschöne Workaround mit Cast:

@SuppressWarnings("unchecked")
T[] arr = (T[]) new Object[10];  // kompiliert mit Warning, Risiko: Heap-Pollution

Hier unterdrückst du die unchecked-Warnung bewusst. Gefährlich ist das, weil zur Laufzeit ein Object[] entsteht: Versuchst du es als typisiertes Array nach außen zu geben, drohen versteckte ClassCastException. Im Zweifel List<T> nehmen.

1. List<String> heißt: NUR Strings. Compile-Time-Garantie, kein Cast beim Rausholen.

2. <T> ist ein Platzhalter. Konkretisiert beim Anlegen: new Box<Integer>().

3. Diamond-Operator <> (Java 7+): rechts kannst du den Typ weglassen, Compiler schließt ihn. Map<String, List<Integer>> m = new HashMap<>();

4. Type-Erasure. Zur Laufzeit existiert das <T> nicht mehr. getClass() zeigt nur ArrayList, nicht ArrayList<String>.

5. PECS-Regel. Producer extends, Consumer super. Wenn unsicher: ohne Wildcard erstmal probieren, beim Methoden-Übergreifen Wildcards einsetzen.

1. new T() versuchen. Geht nicht, weil T zur Laufzeit nicht existiert. Workaround: T als Class<T> Parameter mitgeben oder Supplier<T> verwenden.

2. List<Integer> an List<Number> Parameter übergeben. Geht nicht! Generics sind invariant: List<Integer> ist NICHT subtyp von List<Number>, obwohl Integer extends Number. Wenn du das brauchst: Wildcard List<? extends Number>.

3. Primitive Typen in Generics. List<int> ist Compile-Fehler. Generics brauchen Object-Typen. Nutze die Wrapper: List<Integer>, List<Double>. Autoboxing macht den Rest.

4. Diamond <> vor Java 7 vergessen. In modernem Code IMMER mit Diamond schreiben, sonst Raw-Type-Warnung.

5. Raw-Types verwenden. List l = new ArrayList(); (ohne <...>) ist nur aus Legacy-Kompatibilität noch erlaubt (Java garantiert Rückwärts-Kompatibilität mit Vor-Java-5-Code), aber veraltet und dringend zu vermeiden: Der Compiler erzeugt unchecked- bzw. raw-type-Warnings und du verlierst die komplette Typsicherheit. Raw Types sind formal nicht als @Deprecated markiert, du solltest sie aber wie veralteten Code behandeln und in neuem Code meiden. In neuem Code immer den konkreten Typ oder eine Wildcard angeben.

Beispiel (typischer Klausur-Stil, zur Übung konstruiert).

Gegeben ist eine Methode, die alle Elemente von src nach dst kopiert:

static <T> void copy(List<___> src, List<___> dst) {
    for (T element : src) {
        dst.add(element);
    }
}

(a) Welche Wildcards gehören in die beiden Lücken, damit man z. B. aus einer List<Integer> in eine List<Number> kopieren kann? (b) Begründe mit der PECS-Regel. (c) Warum wäre List<T> für beide Parameter zu streng?

Lösung:

  • (a) static <T> void copy(List<? extends T> src, List<? super T> dst). Damit akzeptiert src eine Liste von T oder einem Subtyp, dst eine Liste von T oder einem Obertyp.
  • (b) src ist der Producer: du liest nur Werte heraus (element kommt aus src), also extends (Producer Extends). dst ist der Consumer: du schreibst nur Werte hinein (dst.add(...)), also super (Consumer Super). Genau das ist PECS.
  • (c) Mit List<T> für beide müssten src und dst exakt denselben Typ-Parameter haben. Wegen der Invarianz von Generics wäre dann copy(List<Integer>, List<Number>) ein Compile-Fehler, obwohl Integer ein Subtyp von Number ist. Die Wildcards lockern genau diese Einschränkung. (Diese Signatur entspricht der echten Collections.copy-API, vereinfacht; die echte API prüft zusätzlich zur Laufzeit, dass dst.size() >= src.size(), sonst eine IndexOutOfBoundsException.)

Probier verschiedene Typ-Parameter aus (z.B. String, Integer, Object). Beobachte: identischer Code, andere Typen, Compile-Fehler erscheinen / verschwinden.

Wähle den Typ T für Box<T> aus und sieh, welche Operationen erlaubt sind und welche der Compiler ablehnt. Probiere etwa, was passiert, wenn du textBox.setInhalt(42) bei einer Box<String> aufrufst, oder zahlBox.setInhalt("Text") bei einer Box<Integer>.

Lade Visualisierung...

Klausur-Tipp: Bei Klausurfragen "Welche Zeile wirft einen Compile-Fehler?", schau auf den deklarierten Generic-Typ und prüfe, ob der zugewiesene Wert/Typ passt. Generics-Fehler werden vom Compiler erkannt, NICHT erst zur Laufzeit.


Drei Demos zum Durchklicken. Klick Step oder Auto und beobachte, wie sich Typ-Check, Variablen und Ausgabe pro Schritt ändern.

1. Bounded Type-Parameter: max() über Comparable

Die generische Methode max läuft durch eine Liste und nutzt compareTo, das nur erlaubt ist, weil T extends Comparable<T> gefordert wird.

Lade Visualisierung...

2. PECS in Aktion: kopiere(src, dst)

Klassische Wildcard-Signatur. src ist Producer (extends), dst ist Consumer (super). Beobachte, wie ein Integer aus der Producer-Liste in die Consumer-Liste wandert.

Lade Visualisierung...

3. Type-Erasure: getClass() ist identisch

Zwei Listen mit verschiedenen Typ-Parametern, aber zur Laufzeit dieselbe Klasse. Der Stepper zeigt, warum der ==-Vergleich true ergibt.

Lade Visualisierung...

Anmelden, um den Fortschritt zu speichern.

Nächster Schritt

Wenn du fertig bist: jetzt üben.

Aktives Abrufen festigt Wissen schneller als nochmal lesen.

War das hilfreich?

Verwandte Themen

  • Variablen und Datentypen
  • Bedingungen
  • Schleifen
  • Funktionen
  • Rekursion

Folgt

  • Collections-Framework

Tools

Bald: Karteikarten · Spaced-Repetition · Mind-Map-Export

Fachliche Qualität
S-Tier · GoldstandardZuletzt geprüft am 11.06.2026

Diese Lerneinheit wurde für typische Bachelor-Klausuren konzipiert. So prüfen wir · Fehler entdeckt? Melde ihn uns oder markiere die fragliche Stelle direkt im Text oben.

Klausur-ÜbersichtKomplette Übersicht: alle Tabs als linearer Text zum Lernen
▾

Alle Tabs der Lerneinheit (Erklärung · Interaktiv verstehen · Praxis-Übung · Klausur-Quiz) als durchgehender Text. Ideal zum Wiederholen vor der Klausur, und für Suchmaschinen wie Google, Bing und KI-Suche (ChatGPT, Perplexity).

Inhalt dieser Übersicht

  1. Erklärung(Erklärung)
  2. Interaktiv verstehen(Visualisierung / Interaktiv)
  3. Praxis-Übung(Quiz / Klausurfragen)
  4. Klausur-Quiz(Quiz / Klausurfragen)
Teil 1·Erklärung

Erklärung

Generics, Type-Parameter

List<String>, Map<Integer, Student>, Optional<T>, diese spitzen Klammern sind Generics. Sie sagen dem Compiler: "Diese Liste enthält NUR Strings, kein Integer, kein Object." Damit fängt Java Tippfehler beim Kompilieren statt erst zur Laufzeit mit einer ClassCastException. Du lernst hier, wie du eigene generische Klassen und Methoden schreibst, was Bounded Type-Parameter (T extends Comparable<T>) bedeuten, wie du Wildcards (<?>, <? extends X>, <? super X>) liest und mit der PECS-Regel anwendest, und warum Type-Erasure dafür sorgt, dass <T> zur Laufzeit verschwindet. In der Klausur löst dir das genau diese Fragetypen: Raw Types erkennen, Wildcards lesen, PECS anwenden, Type-Erasure erklären und Compile-Fehler vorhersagen.

Klausur-Pflicht in 10/13 WInf-Prog-1-Klausuren, Type-Parameter, Wildcards, Type-Erasure sind Standard-Stoff.

Die Idee in einem Satz

Generics: Du parametrisierst eine Klasse oder Methode mit einem Typ, statt einen festen Typ hart zu kodieren. Box<T> ist eine Box für irgendeinen Typ T, der beim Anlegen festgelegt wird.

Warum überhaupt Generics?, der historische Schmerz

Vor Java 5 gab es keine Generics. Listen sahen so aus:

List liste = new ArrayList();
liste.add("Hallo");
liste.add(42);  // kein Fehler! Objekt = alles geht
String s = (String) liste.get(1);  // CRASH zur Laufzeit

Die Liste konnte alles speichern, weil intern Object. Beim Rausholen musstest du casten, und wenn du falsch lagst, ClassCastException zur Laufzeit. Bug findet dich, nicht du den Bug.

Mit Generics:

List<String> liste = new ArrayList<>();
liste.add("Hallo");
liste.add(42);  // ← Compile-Fehler! int passt nicht in List<String>
String s = liste.get(0);  // kein Cast nötig

Type-Safety zur Compile-Zeit. Der ganze Vorteil von Generics in einem Satz.

Eigene generische Klasse: Box<T>

public class Box<T> {
    private T inhalt;

    public void setInhalt(T inhalt) {
        this.inhalt = inhalt;
    }

    public T getInhalt() {
        return inhalt;
    }
}

Nutzen:

Box<String> textBox = new Box<>();
textBox.setInhalt("Hallo");
String s = textBox.getInhalt();  // kein Cast

Box<Integer> zahlBox = new Box<>();
zahlBox.setInhalt(42);
// zahlBox.setInhalt("Text");  // Compile-Fehler!

Das T ist nur ein Platzhalter. Beim Anlegen (new Box<String>()) behandelt der Compiler Box<String> typsicher als Box für Strings: Argumente und Rückgaben werden gegen String geprüft. Im Bytecode wird T später durch Type-Erasure verarbeitet (dazu unten mehr), zur Laufzeit existiert der konkrete Typparameter nicht mehr. Konvention: ein Buchstabe (T für Type, E für Element, K/V für Key/Value).

Generische Methode

Auch einzelne Methoden können generisch sein, unabhängig von der Klasse:

public class Util {
    public static <T> T erstes(List<T> liste) {
        return liste.get(0);
    }
}

String s = Util.<String>erstes(myStringList);  // explizit
String s2 = Util.erstes(myStringList);          // Java schließt T = String

Das <T> vor dem Rückgabetyp deklariert den Type-Parameter dieser Methode. Achte genau auf die Position, dasselbe T taucht in einer Signatur in zwei völlig verschiedenen Rollen auf:

        DEKLARATION          RÜCKGABETYP
            │                     │
            ▼                     ▼
  public static <T>      T      erstes(List<T> liste)
                ───      ─                    ─
                 │       │                    │
   "ab hier ist T   "diese Methode      "Element-Typ der
    ein Typ-Param"   liefert ein T"      Eingabe-Liste"

Das erste <T> (in spitzen Klammern) ist die Deklaration, sie führt den Namen T ein. Jedes weitere T (Rückgabetyp, Parameter) ist eine Verwendung dieses Namens. Ohne das vorangestellte <T> wüsste der Compiler nicht, dass T ein Type-Parameter und kein realer Klassenname ist.

Bounded Type-Parameter: T extends Comparable<T>

Manchmal brauchst du eine Bedingung an T, z.B. "T muss vergleichbar sein":

public static <T extends Comparable<T>> T max(List<T> liste) {
    T grösstes = liste.get(0);
    for (T element : liste) {
        if (element.compareTo(grösstes) > 0) {
            grösstes = element;
        }
    }
    return grösstes;
}

Jetzt darf T nur etwas sein, das Comparable ist, und du darfst compareTo aufrufen.

extends heißt hier sowohl "erbt von" als auch "implementiert" (Interfaces). Java spart sich ein zweites Wort für Interfaces in Generics-Bounds.

Profi-Hinweis: Robuster ist in echtem Java-Code <T extends Comparable<? super T>>, weil dann auch Typen erlaubt sind, deren compareTo von einem Obertyp geerbt ist. Für Prog-1 reicht aber <T extends Comparable<T>>.

Wildcards: <?>, <? extends X>, <? super X>

Wildcards braucht man bei Methoden-Parametern, wenn der konkrete Typ egal sein darf:

WildcardBedeutungBeispiel
List<?>Liste von irgendwas (read-only)universelle Ausgabe-Funktion
List<? extends Number>Liste von Number oder Subklassen (Integer, Double)nur lesen ("Producer")
List<? super Integer>Liste von Integer oder Oberklassen (Number, Object)nur schreiben ("Consumer")

PECS-Regel: Producer Extends, Consumer Super. Wenn die Liste dir Werte liefert, nutze extends. Wenn du Werte reinschreibst, nutze super.

       liefert dir Werte (Producer)          du schreibst Werte rein (Consumer)
            ──── get() ───►                          ◄─── add() ────
  List<? extends Number>  src                 dst  List<? super Integer>
  -> lesen erlaubt, add verboten              -> add erlaubt, get nur als Object

Eselsbrücke: aus einem extends-Producer holst du nur heraus (kommt raus = du liest), in einen super-Consumer steckst du nur hinein (geht rein = du schreibst).

Type-Erasure: der Compiler-Trick

Im fertigen Bytecode verschwindet das <T>! Java ersetzt T durch Object (oder bei Bounds durch die obere Schranke) und fügt Casts ein, wo nötig. List<String> und List<Integer> sind zur Laufzeit dieselbe Klasse.

Merksatz, was bleibt vs. was verschwindet: Der Klassen-Typ existiert zur Laufzeit weiter, String.class ist real und abfragbar. Der generische Typparameter verschwindet: List<String> und List<Integer> haben dieselbe Runtime-Klasse (ArrayList). Es verschwinden also nicht alle Typinformationen, nur die <...>-Parameter.

So sieht derselbe Code vor und nach der Erasure aus:

VORHER (dein Quellcode, Compile-Zeit)     NACHHER (Bytecode, Laufzeit-Sicht)
─────────────────────────────────────     ──────────────────────────────────
class Box<T> {                            class Box {
    T inhalt;                                 Object inhalt;        // T -> Object
    T getInhalt() { return inhalt; }          Object getInhalt() { return inhalt; }
}                                         }

Box<String> b = new Box<>();              Box b = new Box();
String s = b.getInhalt();                 String s = (String) b.getInhalt();  // Cast eingefügt

Der Compiler garantiert die Typsicherheit also VOR der Erasure und packt danach die nötigen Casts selbst rein. Konsequenzen:

List<String> l1 = new ArrayList<>();
List<Integer> l2 = new ArrayList<>();
l1.getClass() == l2.getClass();  // true! Beide geben ArrayList.class zurück (true, weil == hier Identity prüft, also dasselbe Class-Objekt im Speicher, nicht equals())

// und das hier ist KEIN Compile-Fehler:
if (obj instanceof List) { ... }  // erlaubt
// aber das hier IST ein Compile-Fehler:
if (obj instanceof List<String>) { ... }  // nicht erlaubt

Du kannst auch keine Generic-Arrays anlegen:

T[] array = new T[10];  // Compile-Fehler
List<String>[] arr = new List<String>[5];  // Compile-Fehler (parameterisierter Typ)

Was hingegen erlaubt ist, zum Kontrast: ein Array mit unbounded Wildcard, weil List<?> ein reifizierbarer Typ ist (keine konkrete Typ-Information, die durch Erasure verloren ginge):

List<?>[] arr = new List<?>[5];  // erlaubt

Brauchst du wirklich ein generisches Array, gibt es zwei Wege. Der saubere: einfach List<T> statt Array verwenden, dann hast du das Problem nie. Der unschöne Workaround mit Cast:

@SuppressWarnings("unchecked")
T[] arr = (T[]) new Object[10];  // kompiliert mit Warning, Risiko: Heap-Pollution

Hier unterdrückst du die unchecked-Warnung bewusst. Gefährlich ist das, weil zur Laufzeit ein Object[] entsteht: Versuchst du es als typisiertes Array nach außen zu geben, drohen versteckte ClassCastException. Im Zweifel List<T> nehmen.

Klausur-Faustregeln

1. List<String> heißt: NUR Strings. Compile-Time-Garantie, kein Cast beim Rausholen.

2. <T> ist ein Platzhalter. Konkretisiert beim Anlegen: new Box<Integer>().

3. Diamond-Operator <> (Java 7+): rechts kannst du den Typ weglassen, Compiler schließt ihn. Map<String, List<Integer>> m = new HashMap<>();

4. Type-Erasure. Zur Laufzeit existiert das <T> nicht mehr. getClass() zeigt nur ArrayList, nicht ArrayList<String>.

5. PECS-Regel. Producer extends, Consumer super. Wenn unsicher: ohne Wildcard erstmal probieren, beim Methoden-Übergreifen Wildcards einsetzen.

Häufige Stolpersteine

1. new T() versuchen. Geht nicht, weil T zur Laufzeit nicht existiert. Workaround: T als Class<T> Parameter mitgeben oder Supplier<T> verwenden.

2. List<Integer> an List<Number> Parameter übergeben. Geht nicht! Generics sind invariant: List<Integer> ist NICHT subtyp von List<Number>, obwohl Integer extends Number. Wenn du das brauchst: Wildcard List<? extends Number>.

3. Primitive Typen in Generics. List<int> ist Compile-Fehler. Generics brauchen Object-Typen. Nutze die Wrapper: List<Integer>, List<Double>. Autoboxing macht den Rest.

4. Diamond <> vor Java 7 vergessen. In modernem Code IMMER mit Diamond schreiben, sonst Raw-Type-Warnung.

5. Raw-Types verwenden. List l = new ArrayList(); (ohne <...>) ist nur aus Legacy-Kompatibilität noch erlaubt (Java garantiert Rückwärts-Kompatibilität mit Vor-Java-5-Code), aber veraltet und dringend zu vermeiden: Der Compiler erzeugt unchecked- bzw. raw-type-Warnings und du verlierst die komplette Typsicherheit. Raw Types sind formal nicht als @Deprecated markiert, du solltest sie aber wie veralteten Code behandeln und in neuem Code meiden. In neuem Code immer den konkreten Typ oder eine Wildcard angeben.

Klausur-Beispielaufgabe

Beispiel (typischer Klausur-Stil, zur Übung konstruiert).

Gegeben ist eine Methode, die alle Elemente von src nach dst kopiert:

static <T> void copy(List<___> src, List<___> dst) {
    for (T element : src) {
        dst.add(element);
    }
}

(a) Welche Wildcards gehören in die beiden Lücken, damit man z. B. aus einer List<Integer> in eine List<Number> kopieren kann? (b) Begründe mit der PECS-Regel. (c) Warum wäre List<T> für beide Parameter zu streng?

Lösung:

  • (a) static <T> void copy(List<? extends T> src, List<? super T> dst). Damit akzeptiert src eine Liste von T oder einem Subtyp, dst eine Liste von T oder einem Obertyp.
  • (b) src ist der Producer: du liest nur Werte heraus (element kommt aus src), also extends (Producer Extends). dst ist der Consumer: du schreibst nur Werte hinein (dst.add(...)), also super (Consumer Super). Genau das ist PECS.
  • (c) Mit List<T> für beide müssten src und dst exakt denselben Typ-Parameter haben. Wegen der Invarianz von Generics wäre dann copy(List<Integer>, List<Number>) ein Compile-Fehler, obwohl Integer ein Subtyp von Number ist. Die Wildcards lockern genau diese Einschränkung. (Diese Signatur entspricht der echten Collections.copy-API, vereinfacht; die echte API prüft zusätzlich zur Laufzeit, dass dst.size() >= src.size(), sonst eine IndexOutOfBoundsException.)
Teil 2·Visualisierung / Interaktiv

Interaktiv verstehen

Generics-Type-Toggle

Probier verschiedene Typ-Parameter aus (z.B. String, Integer, Object). Beobachte: identischer Code, andere Typen, Compile-Fehler erscheinen / verschwinden.

Wähle den Typ T für Box<T> aus und sieh, welche Operationen erlaubt sind und welche der Compiler ablehnt. Probiere etwa, was passiert, wenn du textBox.setInhalt(42) bei einer Box<String> aufrufst, oder zahlBox.setInhalt("Text") bei einer Box<Integer>.

Interaktive Visualisierung

Interaktive Komponente: probiere sie im Topic-Player oben aus.

Klausur-Tipp: Bei Klausurfragen "Welche Zeile wirft einen Compile-Fehler?", schau auf den deklarierten Generic-Typ und prüfe, ob der zugewiesene Wert/Typ passt. Generics-Fehler werden vom Compiler erkannt, NICHT erst zur Laufzeit.


Code-Stepper: Schritt für Schritt

Drei Demos zum Durchklicken. Klick Step oder Auto und beobachte, wie sich Typ-Check, Variablen und Ausgabe pro Schritt ändern.

1. Bounded Type-Parameter: max() über Comparable

Die generische Methode max läuft durch eine Liste und nutzt compareTo, das nur erlaubt ist, weil T extends Comparable<T> gefordert wird.

Interaktive Visualisierung

Interaktive Komponente: probiere sie im Topic-Player oben aus.

2. PECS in Aktion: kopiere(src, dst)

Klassische Wildcard-Signatur. src ist Producer (extends), dst ist Consumer (super). Beobachte, wie ein Integer aus der Producer-Liste in die Consumer-Liste wandert.

Interaktive Visualisierung

Interaktive Komponente: probiere sie im Topic-Player oben aus.

3. Type-Erasure: getClass() ist identisch

Zwei Listen mit verschiedenen Typ-Parametern, aber zur Laufzeit dieselbe Klasse. Der Stepper zeigt, warum der ==-Vergleich true ergibt.

Interaktive Visualisierung

Interaktive Komponente: probiere sie im Topic-Player oben aus.

Teil 3·Quiz / Klausurfragen

Praxis-Übung

Generics, Praxis-Übung

6 Aufgaben zu Type-Parameter, Wildcards, Type-Erasure.

Klausurfragen mit Lösungen (6)

F1.Was ist der Hauptvorteil von Generics?

Antwort: Type-Safety zur Compile-Zeit

Erklärung: Generics geben dir Type-Safety BEIM KOMPILIEREN. Type-Mismatches werden vom Compiler erkannt, statt erst zur Laufzeit als ClassCastException zu crashen. Keine Performance-Vorteile (eher gleich), Speicher unverändert.

F2.Welche Zeile erzeugt einen Compile-Fehler?
List<String> liste = new ArrayList<>();
liste.add("Hallo");
liste.add(42);
String s = liste.get(0);

Antwort: Zeile 3: liste.add(42)

Erklärung: Zeile 3 ist ein Compile-Fehler: 42 (Integer) passt nicht in List<String>. Das ist exakt der Schutz, den Generics geben. Zeile 4 wäre ohne Generics ein Cast (String), mit Generics nicht nötig.

F3.Zur Laufzeit unterscheidet die JVM zwischen List<String> und List<Integer>.

Antwort: Falsch

Erklärung: FALSCH. Type-Erasure: zur Laufzeit sind beide einfach ArrayList. Die Typ-Information ist nur zur Compile-Zeit da. Deshalb: liste.getClass() == liste2.getClass() ist TRUE, auch bei unterschiedlichen Generic-Typen.

Typ: Wahr/Falsch

F4.Ist List<Integer> ein Subtyp von List<Number>?

Antwort: Nein, Generics sind invariant

Erklärung: Generics sind INVARIANT, auch wenn Integer Subtyp von Number ist, ist List<Integer> KEIN Subtyp von List<Number>. Wäre fatal, weil du sonst einen Double in eine List<Integer> reinschmuggeln könntest. Wenn du das brauchst: Wildcard List<? extends Number>.

F5.Welche Aussagen über Wildcards sind RICHTIG?

Richtige Antworten: List<?> erlaubt jeden Typ als Inhalt; List<? extends Number> erlaubt nur Lesen, kein Schreiben (außer null); List<? super Integer> erlaubt das Hinzufügen von Integer-Werten; PECS: Producer extends, Consumer super

Erklärung: Richtig: List<?> Beliebig, extends → nur lesen, super → schreiben ok, PECS-Regel. Falsch: Wildcards sind Compile-Time, keine Perf-Auswirkung. List<?> != List<Object>: List<String> ist Subtyp von List<?> aber NICHT von List<Object>.

Typ: Multi-Select

F6.Ordne Generic-Konstrukt zur Bedeutung zu:

Zuordnungen:

  • <T> → Type-Parameter (Platzhalter, wird konkretisiert)
  • <T extends Comparable<T>> → Bounded: T muss Comparable sein
  • <?> → Wildcard: irgendein Typ
  • <? super Integer> → Integer oder Oberklasse von Integer

Erklärung: Standard-Generic-Vokabular. Sitzen lernen, kommt in 1-2 Klausurfragen vor.

Typ: Zuordnung

Teil 4·Quiz / Klausurfragen

Klausur-Quiz

Klausurfragen mit Lösungen (6)

F1.Welcher Buchstabe ist Konvention für 'Type-Parameter einer Liste'?

Antwort: E

Erklärung: E = Element (List<E>, Set<E>). T = generischer Type, K = Key, V = Value (Map<K,V>). Nur Konvention, technisch wäre jeder Buchstabe erlaubt.

F2.Was bedeutet der Diamond-Operator <> in: List<String> liste = new ArrayList<>();?

Antwort: Diamond: Compiler schließt den Typ aus dem linken Kontext

Erklärung: Seit Java 7 darf der Typ auf der rechten Seite weggelassen werden, wenn er aus dem linken Kontext (Variable-Deklaration) ableitbar ist. Compiler ergänzt automatisch <String>.

F3.Man kann ein generisches Array anlegen: T[] arr = new T[10];

Antwort: Falsch

Erklärung: FALSCH. Wegen Type-Erasure ist das Compile-Fehler. Java weiß zur Laufzeit nicht, was T war, und kann das Array nicht typsicher allokieren. Workaround: List<T> verwenden oder (T[]) Array.newInstance(...).

Typ: Wahr/Falsch

F4.Welche Methoden-Signatur erlaubt 'kopiere alles von src nach dst', wenn dst die Werte vom Typ T oder einer Oberklasse aufnehmen darf?

Antwort: <T> void kopiere(List<? extends T> src, List<? super T> dst)

Erklärung: PECS in Aktion! src ist PRODUCER → extends (src.get(i) liefert T oder Subtyp). dst ist CONSUMER → super (dst.add() akzeptiert T oder Subtyp). Klassische Java-API-Signatur (siehe Collections.copy).

F5.Generics sind {{1}}: List<Integer> ist KEIN Subtyp von List<Number>. Zur Laufzeit existiert das <T> nicht mehr, das nennt man {{2}}. Die PECS-Regel lautet: Producer {{3}}, Consumer {{4}}.

Lösungen pro Lücke:

  • {{1}}: invariant
  • {{2}}: Type-Erasure / Type Erasure / Typ-Erasure
  • {{3}}: extends
  • {{4}}: super

Erklärung: Generics-Kern-Vokabular. Invariant, Type-Erasure, PECS, wer diese 3 Begriffe sicher erklären kann, hat Generics verstanden.

Typ: Lückentext

F6.Was ist der korrekte Compile-Schritt für List<String> l = new ArrayList<>(); l.add("x"); String s = l.get(0);?

Richtige Reihenfolge:

  1. Compiler prüft alle add(...) gegen Generic-Typ
  2. Type-Erasure: T → Object im Bytecode
  3. Compiler fügt automatisch (String)-Cast beim get(0) ein
  4. Zur Laufzeit: ArrayList speichert Object-Referenzen

Erklärung: Compile-Zeit: Typ-Check. Bytecode-Generierung: Erasure + Cast-Insertion. Laufzeit: nur noch Object. So liefert Java Type-Safety ohne JVM-Generics.

Typ: Reihenfolge

Zur KategorieProgrammiergrundlagen.Mehr Themen entdeckenZum Themen-Hub.

UniProMax ist eine themenbasierte Lernplattform für Studierende an deutschen Unis.

Wir glauben, dass Verstehen besser ist als Auswendiglernen. Wir bauen Lerneinheiten die zeigen statt erzählen. Code, Visualisierung, Quiz. Auf Deutsch.

Marke

UniProMaxUniProMax

Themenbasiert, visuell, interaktiv.

Inhalte

  • Alle Themen (Hub)
  • Programmiergrundlagen
  • Algorithmen
  • Mathematik
  • Statistik
  • Datenbanken
  • Rechnungswesen
  • VWL

Studiengang-Filter

  • Informatik
  • Wirtschaftsinformatik
  • BWL
  • Data Science
  • VWL
  • Wirtschaftsingenieurwesen
  • Mathe
  • Psychologie
  • weitere Studiengänge folgen

Plattform

  • Mein Fortschritt
  • Impressum
  • Datenschutz
© 2026 UniProMaxAlle Systeme onlinev0.2 / Sommersemester 2026
UniProMaxUniProMaxUniProMaxUniProMax