EMZETT.
Login

Kurz: Generics erlauben, Klassen und Methoden für beliebige Typen zu schreiben, ohne die Typsicherheit aufzugeben. Du kennst sie schon von List<String>.

Teil des Kurses Java

Kapitel 15 von 22 im Kurs Java (Abschnitt „Fortgeschritten“). Mit Fortschritt, Quiz und Zertifikat auf der Lernseite.

Generics erlauben, Klassen und Methoden für beliebige Typen zu schreiben, ohne die Typsicherheit aufzugeben. Du kennst sie schon von List<String>.

Das Problem ohne Generics

Vor Java 5 speicherten Collections nur Object. Man musste umwandeln und konnte sich leicht irren:

import java.util.*;
 
public class Ohne {
    @SuppressWarnings({"rawtypes", "unchecked"})
    public static void main(String[] args) {
        List liste = new ArrayList();            // "raw type": nicht tun!
        liste.add("Text");
        liste.add(42);
        try {
            String s = (String) liste.get(1);    // ClassCastException erst zur Laufzeit
        } catch (ClassCastException e) {
            System.out.println("Laufzeitfehler: " + e.getMessage().substring(0, 40));
        }
    }
}

Ausgabe:

Laufzeitfehler: class java.lang.Integer cannot be cast t

Mit List<String> meldet der Compiler den Fehler schon beim Übersetzen.

Generische Klasse

public class Kiste {
    static class Box<T> {
        private T inhalt;
        Box(T inhalt) { this.inhalt = inhalt; }
        T get() { return inhalt; }
        void set(T inhalt) { this.inhalt = inhalt; }
    }
    static class Paar<A, B> {
        final A erstes; final B zweites;
        Paar(A a, B b) { erstes = a; zweites = b; }
        Paar<B, A> tauschen() { return new Paar<>(zweites, erstes); }
        @Override public String toString() { return "(" + erstes + ", " + zweites + ")"; }
    }
 
    public static void main(String[] args) {
        Box<String> text = new Box<>("Hallo");
        Box<Integer> zahl = new Box<>(42);
        String s = text.get();                     // kein Cast nötig
        int n = zahl.get() + 1;
        System.out.println(s + " " + n);
        Paar<String, Integer> p = new Paar<>("Alter", 17);
        System.out.println(p + " " + p.tauschen());
    }
}

Ausgabe:

Hallo 43
(Alter, 17) (17, Alter)

<> (Diamond) lässt den Compiler den Typ ergänzen. T, E, K, V sind übliche Namen für Typparameter.

Generische Methoden

import java.util.*;
 
public class Methoden {
    static <T> T erstes(List<T> liste) { return liste.isEmpty() ? null : liste.get(0); }
 
    static <T> void tausche(T[] a, int i, int j) { T t = a[i]; a[i] = a[j]; a[j] = t; }
 
    static <T extends Comparable<T>> T maximum(List<T> liste) {       // T muss vergleichbar sein
        T max = liste.get(0);
        for (T x : liste) if (x.compareTo(max) > 0) max = x;
        return max;
    }
 
    public static void main(String[] args) {
        System.out.println(erstes(List.of("a", "b")) + " " + erstes(List.<Integer>of()));
        String[] w = {"x", "y", "z"};
        tausche(w, 0, 2);
        System.out.println(Arrays.toString(w));
        System.out.println(maximum(List.of(3, 9, 4)) + " " + maximum(List.of("kiwi", "apfel", "zitrone")));
    }
}

Ausgabe:

a null
[z, y, x]
9 zitrone

<T extends Comparable<T>> schränkt T ein: Nur Typen, die sich vergleichen lassen.

Wildcards

? steht für “irgendein Typ”. Das ist nötig, weil List<Integer> keine Unterklasse von List<Number> ist:

import java.util.*;
 
public class Wildcards {
    static double summe(List<? extends Number> zahlen) {       // lesen: Number oder Unterklasse
        double s = 0;
        for (Number n : zahlen) s += n.doubleValue();
        return s;
    }
    static void fuelle(List<? super Integer> ziel) {            // schreiben: Integer oder Oberklasse
        for (int i = 1; i <= 3; i++) ziel.add(i);
    }
    public static void main(String[] args) {
        System.out.println(summe(List.of(1, 2, 3)) + " " + summe(List.of(1.5, 2.5)));
        List<Number> alles = new ArrayList<>();
        fuelle(alles);
        System.out.println(alles);
    }
}

Ausgabe:

6.0 4.0
[1, 2, 3]

Merkregel PECS: Producer Extends, Consumer Super. Wer nur liest, nimmt extends; wer nur schreibt, nimmt super.

Typlöschung (Type Erasure)

Generics gibt es nur zur Übersetzungszeit. Zur Laufzeit werden Typparameter gelöscht. Daraus folgt: Kein new T(), kein T[], kein instanceof List<String>, und List<String> und List<Integer> haben dieselbe Laufzeitklasse:

import java.util.*;
 
public class Loeschung {
    public static void main(String[] args) {
        System.out.println(new ArrayList<String>().getClass() == new ArrayList<Integer>().getClass());
    }
}

Ausgabe:

true

Generische Schnittstellen

import java.util.*;
 
public class Speicher {
    interface Repository<T, ID> {
        void speichere(T t);
        Optional<T> finde(ID id);
        List<T> alle();
    }
    record Nutzer(int id, String name) {}
 
    static class MemRepo implements Repository<Nutzer, Integer> {
        private final Map<Integer, Nutzer> daten = new LinkedHashMap<>();
        public void speichere(Nutzer n) { daten.put(n.id(), n); }
        public Optional<Nutzer> finde(Integer id) { return Optional.ofNullable(daten.get(id)); }
        public List<Nutzer> alle() { return new ArrayList<>(daten.values()); }
    }
    public static void main(String[] args) {
        Repository<Nutzer, Integer> repo = new MemRepo();
        repo.speichere(new Nutzer(1, "Mia"));
        repo.speichere(new Nutzer(2, "Tom"));
        System.out.println(repo.finde(2).map(Nutzer::name).orElse("?") + " " + repo.finde(9).isPresent() + " " + repo.alle().size());
    }
}

Ausgabe:

Tom false 2

Merke

  • Generics = Typparameter: class Box<T>, <T> void f(T x)
  • Der Compiler prüft Typen und spart Casts
  • T extends Comparable<T> schränkt Typparameter ein
  • Wildcards: ? extends T zum Lesen, ? super T zum Schreiben (PECS)
  • Typlöschung: zur Laufzeit gibt es die Typparameter nicht mehr
  • Keine “raw types” (List ohne <>)

Übungsaufgabe

Schreibe eine generische Klasse Stapel<T> mit push, pop und istLeer auf Basis einer ArrayList.

Quiz zur Selbstkontrolle

Weiter im Kurs

Zurück: Ausnahmen (Exceptions)

Weiter: Lambdas und Streams

Alle Kapitel: Java im Überblick