Zum Hauptinhalt springen

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.

Box.java
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.

MainClass.java
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.

Box.java
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.

MainClass.java
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);
}

}
info

Die Typisierung kann entweder explizit oder implizit über den Diamantenoperator <> erfolgen.

info

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.

MainClass.java
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.

TypparameterEinsatzbereich
T, U, V, W...Datentyp (Type)
EElement einer Datensammlung (Element)
KSchlüssel eines Assoziativspeichers (Key)
VWert 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.

Box.java
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.

MainClass.java
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
}

}