Generische Programmierung
Quellcode sollte so allgemein wie möglich geschrieben werden, damit er für unterschiedliche Datentypen und Datenstrukturen wiederverwendet werden kann. In Java wird dieses Ziel mit generischen Datentypen erreicht — also Klassen, die mit verschiedenen Typen parametrisiert werden können.
Generische Klassen ohne Java Generics
Auch ohne Java Generics lässt sich in Java mit der Klasse Object generisch
programmieren. Der Nachteil: Durch den Upcast auf Object gehen die
spezifischen Methoden der Originalklasse verloren, und der notwendige Downcast
beim Auslesen kann zu Laufzeitfehlern führen.
Die Klasse Box ermöglicht das Speichern einer beliebig typisierten
Information.
public class Box {
private Object content;
public void set(Object content) {
this.content = content;
}
public Object get() {
return content;
}
}
In der main-Methode der Startklasse wird zunächst eine ganze Zahl in einer Box gespeichert und anschließend wieder ausgelesen. Die Umwandlung der ganzen Zahl in eine Zeichenkette führt erst zur Laufzeit zu einem Fehler.
public class MainClass {
public static void main(String[] args) {
Box box = new Box();
box.set(5);
String i = (String) box.get(); // Laufzeitfehler: int ist kein String
System.out.println(i);
}
}
Generische Klassen mit Java Generics
Klassen und Methoden können in Java mit Typparametern versehen werden. Diese
werden in spitzen Klammern <> notiert und stellen Platzhalter für konkrete
Datentypen dar. Beim Kompilieren ersetzt der Compiler alle generischen
Informationen durch die konkreten Typen. Dadurch entsteht statische
Typsicherheit, die viele Fehler bereits zur Kompilierzeit aufdeckt.
Die generische Klasse Box<T> ermöglicht das Speichern einer beliebig
typisierten Information mit Hilfe des Typparameters T.
public class Box<T> {
private T content;
public void set(T content) {
this.content = content;
}
public T get() {
return content;
}
}
In der main-Methode der Startklasse wird zunächst eine ganze Zahl in einer Box gespeichert und anschließend wieder ausgelesen. Die Umwandlung der ganzen Zahl in eine Zeichenkette führt aufgrund der statischen Typsicherheit zu einem Kompilierungsfehler.
public class MainClass {
public static void main(String[] args) {
Box<Integer> box = new Box<>();
box.set(5);
String i = box.get(); // Kompilierungsfehler: Integer ist kein String
System.out.println(i);
}
}
Die Typisierung kann entweder explizit oder implizit über den Diamantenoperator
<> erfolgen.
Typparameter können auf die Unterklassen einer bestimmten Klasse eingeschränkt
werden. Dadurch kann in der generischen Klasse auf Attribute und Methoden der
angegebenen Klasse zugegriffen werden. Die Angabe eines eingeschränkten
Typparameters erfolgt über den Zusatz extends sowie die Angabe der
entsprechenden Klasse.
Generische Methoden mit Java Generics
Die generische Methode <T> int getIndex(value: T, values: T[]) gibt den Index
eines beliebig typisierten gesuchten Wertes innerhalb eines gleichtypisierten
Feldes zurück.
public class MainClass {
public static void main(String[] args) {
System.out.println(getIndex(5, new Integer[] {3, 5, 2, 4, 1}));
}
public static <T> int getIndex(T value, T[] values) {
for (int i = 0; i < values.length; i++) {
if (values[i].equals(value)) {
return i;
}
}
return -1;
}
}
Namensrichtlinien für Typparameter
Um den Einsatzbereich von Typparametern in generischen Klassen und Methoden zu kennzeichnen, sollten die folgenden etablierten Kürzel verwendet werden.
| Typparameter | Einsatzbereich |
|---|---|
| T, U, V, W... | Datentyp (Type) |
| E | Element einer Datensammlung (Element) |
| K | Schlüssel eines Assoziativspeichers (Key) |
| V | Wert eines Assoziativspeichers (Value) |
Varianz
Mit dem Wildcard-Typ ? kann bei der Deklaration einer generischen Klasse ein
unbestimmter Typ angegeben werden. Dieser kann gar nicht (Bivarianz), nach
oben (Kovarianz), nach unten (Kontravarianz) oder in beide Richtungen
(Invarianz) eingeschränkt werden.
Die generische Klasse Box<T> ermöglicht das Speichern einer beliebig
typisierten Information.
public class Box<T> {
private T content;
public void set(T content) {
this.content = content;
}
public T get() {
return content;
}
}
In der main-Methode der Startklasse werden verschiedene Boxen unterschiedlich
deklariert und initialisiert. Dabei gilt: String und Integer sind
Unterklassen von Object, Integer ist außerdem eine Unterklasse von Number.
public class MainClass {
public static void main(String[] args) {
Box<?> bivariantBox;
bivariantBox = new Box<Object>();
bivariantBox = new Box<Number>();
bivariantBox = new Box<Integer>();
bivariantBox = new Box<String>();
Box<? extends Number> covariantBox;
covariantBox = new Box<Object>(); // Kompilierungsfehler
covariantBox = new Box<Number>();
covariantBox = new Box<Integer>();
covariantBox = new Box<String>(); // Kompilierungsfehler
Box<? super Number> contravariantBox;
contravariantBox = new Box<Object>();
contravariantBox = new Box<Number>();
contravariantBox = new Box<Integer>(); // Kompilierungsfehler
contravariantBox = new Box<String>(); // Kompilierungsfehler
Box<Number> invariantBox;
invariantBox = new Box<Object>(); // Kompilierungsfehler
invariantBox = new Box<Number>();
invariantBox = new Box<Integer>(); // Kompilierungsfehler
invariantBox = new Box<String>(); // Kompilierungsfehler
}
}