Ein zentrales Thema beim Aufbau eines objektorientierten Programms ist die Wiederverwendung. Und da machen sich generische Klassen ganz gut.
Was ist eine generische Klasse?
Eine generische Klasse ist ein Template: eine Vorlage, welche zur Generierung von Klassen verwendet wird:
- Methoden und Eigenschaften der Klasse stehen fest
- jedoch die Typen, die verwendet werden (sei es als Übergabe, Rückgabe oder Typ der Eigenschaft) sind unbekannt
Beispiele aus den Bibliotheken
Bekannte Beispiele für generische Klassen aus den Bibliotheken von Java oder auch C# sind unter anderem List, ArrayList, HashSet oder auch die Map bwz. das Dictionary.
Schauen wir uns eine List<> an. Diese ist in der Java API wie folgt definiert (hier auszugsweise angegeben).

Ich habe hier zwei Methoden des Typs List herausgesucht. Die Methode add für das Hinzufügen eines Elements verwendet als Übergabeparameter eine Variable vom Typ unspezifizierten Typ E. Die Methode remove hat das Rückgabetyp ebenfalls das unspezifizierte E.
Bei Deklaration eines Objektes vom Typ List<> bin ich gezwungen, in den <> Klammern den Typ anzugeben, welcher in der List verwendet werden soll.
Nehme ich beispielsweise eine Liste von ganzen Zahlen, so erhalte ich folgenden Code:
List<Integer> liste = new ArrayList<>();
Damit ersetzt der Compiler den Typ E mit Integer, so dass ich nur noch Integer-Werte in der add-Methode verwenden kann und auch nur noch einen Integer-Wert bei Ausführung der remove-Methode erhalten werde.
Erstellung einer eigenen generischen Klasse
Generische Klassen können wir nun auch selber erstellen. Als Beispiel habe ich mir den Gemüseanbau überlegt. Wir definieren eine Klasse Gemüsebeet, auf welchem wir entweder Möhren oder Kohlrabi oder Beides anbauen werden.
Zunächst einmal erstellen wir eine Vererbungshierarchie: Möhre und Kohlrabi sind Gemüse.

Eine weitere Klasse – der BeetMix – dient zum Anbau und der Aufzucht von verschiedenen Gemüsesorten. Auf dem Beet befindet sich Gemüse. Es handelt sich um eine Aggregationsbeziehung:

Damit wären wir in der Lage, in einem Programm ein Gemüsebeet (genauer ein Objekt der Klasse BeetMix) zu erschaffen, welchem wir verschiedene Gemüsesorten (durch die Vererbungsbeziehung sowohl Möhren als auch Kohlrabi) anpflanzen können.
class Gemüse{}
class Möhre extends Gemüse{}
class Kohlrabi extends Gemüse{}
class BeetMix{
private List<Gemüse> pflanzen = new ArrayList<>();
public void anpflanzen(Gemüse was){pflanzen.add(was));}}
class Programm{
public static void main(String... args)
{
BeetMix beet = new BeetMix();
beet.anbauen(new Möhre());
beet.anbauen(new Kohlrabi());}}
Was aber ist, wenn ich nur eine Sorte Gemüse – sprich entweder nur Kohlrabi oder nur Möhren – anbauen möchte, aber nur eine Klasse BeetMix im Programm existieren soll.
Eine Idee wäre:
- wir erstellen eine Klasse Möhrenbeet und bauen eine Aggregationsbeziehung zur Klasse Möhre auf
- wir erstellen eine Klasse Kohlrabibeet und bauen eine Aggregationsbeziehung zur Klasse Kohlrabi auf.
Das funktioniert, ist aber suboptimal. Wenn wir nur dieses eine Programm haben und das Klassendiagramm niemals erweitert wird, können wir das so lassen. Wenn wir uns aber nicht sicher sind, ob nicht vielleicht in ferner (oder auch naher) Zukunft eine dritte oder auch vierte Gemüsesorte ins Programm aufgenommen wird, dann haben wir im Programmcode zu viel Redundanz.die Klassen für die einzelnen Gemüsebeete sind gleich aufgebaut
Es gilt:
- es wird immer etwas angepflanzt
- es wird immer etwas geerntet
- die Methoden der Klasse BeetMix sind identisch
Nicht nur die Redundanz innerhalb der einzelnen Klassen ist nicht so toll – nein, auch dass wir das vorhandene Klassendiagramm anpassen müssten.
Die Idee
Wir erstellen eine generische Klasse für ein Gemüsebeet.

Es besteht weiterhin eine Aggregation zwischen der Klasse Beet und der abstrakten Klasse Gemüse, jedoch wird jeder Datentyp Gemüse in der Klasse Beet durch eine fiktiven Typparameter T ersetzt. In der Deklaration der Klasse wird angegeben, dass T vom Typ Gemüse ist.
class Gemüsebeet<T extends Gemüse>
{
private List pflanzen = new ArrayList<>();
public void anbauen(T was)
{
pflanzen.add(was);
}
}
Wir können nun unterschiedliche Gemüsebeete erzeugen:
ein Gemüsebeet mit Möhren und Kohlrabi
Beet<Gemüse> beet = new Beet<>();
ein Gemüsebeet mit Möhren
Beet<Möhren> beet = new Beet<>();
ein Gemüsebeet mit Kohlrabi
Beet<Kohlrabi> beet = new Beet<>();
Wenn ich nun eine neue Gemüsesorte in mein Programm aufnehmen möchte – zum Beispiel Paprika – dann muss ich sicherlich weiterhin das vorhandene Klassendiagramm erweitern. Allerdings brauche ich nur eine Klasse Paprika als Unterklasse der Klasse Gemüse definieren.
class Paprika extends Gemüse{}
class Programm{
public static void main(String... args){
Beet<Paprika> beet = new Beet<>();
beet.anpflanzen(new Paprika()); //Compiler sagt Ja
beet.anpflanzen(new Möhre()); //Compiler sagt Nein
}}