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
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 tMit 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:
trueGenerische 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 2Merke
- 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 Tzum Lesen,? super Tzum Schreiben (PECS) - Typlöschung: zur Laufzeit gibt es die Typparameter nicht mehr
- Keine “raw types” (
Listohne<>)
Übungsaufgabe
Schreibe eine generische Klasse Stapel<T> mit push, pop und istLeer auf Basis einer ArrayList.
Quiz zur Selbstkontrolle
Was bedeutet List<? extends Number>?
- Eine Liste von Number oder einer Unterklasse, zum Lesen (richtig)
- Eine Liste aus beliebigen Objekten
- Eine Liste nur aus Number
- Eine leere Liste
Was ist Typlöschung?
- Typparameter existieren nur beim Übersetzen, nicht zur Laufzeit (richtig)
- Typen werden gelöscht, wenn man sie nicht braucht
- Der Compiler löscht Klassen
- Ein Speicherfehler
Warum soll man raw types vermeiden?
- Sie umgehen die Typprüfung (richtig)
- Sie sind langsamer
- Sie sind veraltet im Speicher
- Sie funktionieren nicht
Weiter im Kurs
Zurück: Ausnahmen (Exceptions)
Weiter: Lambdas und Streams
Alle Kapitel: Java im Überblick