Was sind generische Datentypen?v.5.0

Unter generischer Programmierung versteht man ein Programmierparadigma, das bei der Implementierung von Klassen, Interfaces und Methoden die Verwendung von sog. Typvariablen erlaubt. Der Typ dieser Variablen wird bei der Implementierung zunächst durch Platzhalter repräsentiert und erst zum Zeitpunkt der Anwendung konkretisiert.
Ein generischer Datentyp ermöglicht es so, die Funktionalität ein und der selben Klasse auf unterschiedliche Datentypen anzuwenden und die Typsicherheit zu erhöhen.

ArrayList als Beispiel

Als einführendes Beispiel sei eine ArrayList betrachtet, die Objekte vom Typ Object speichert. Da alle nicht primitiven Typen in Java letztlich von Object abgeleitet sind, können sie auch in einer ArrayList abgelegt werden und das sogar in gemischter Form, wie hier am Beispiel eines Strings und eines Integers gezeigt:

ArrayList al = new ArrayList();
al.add("Hallo");
al.add(14); // Autoboxing, als Integer gespeichert

Was zunächst recht praktisch erscheint, birgt jedoch Gefahren, da der Compiler eventuell hieraus erwachsende Probleme nicht erkennt. Aus seiner Sicht ist die Liste ja korrekt mit Daten des Supertyps Object gefüllt.
Liest man die Liste bspw. auf die folgende Weise aus und geht irrtümlicherweise davon aus, dass sie durchgängig mit String-Objekten gefüllt ist, so wird zur Laufzeit eine ClassCastException geworfen, da unter dem Index 1 ein Integer gespeichert ist, für den die Methode toUpperCase() nicht verwendet werden kann. Eine Gefahr, die, etwa bei dynamischer Füllung der Liste zur Laufzeit, nicht unterschätzt werden sollte.

for(int i=0; i<al.size(); ++i) {
    System.out.println(((String)al.get(i)).toLowerCase()); // ClassCastException
}

Typparameter

Um dem Compiler bereits im Vorfeld den zu speichernden Datentyp bekannt zu machen, werden bei der Objektbildung eines generischen Typs, also z.B. bei der o.a. ArrayList, sog. Typparameter angegeben. Dies geschieht durch Notation des gewünschten Datentyps in spitzen Klammern hinter dem Bezeichner des generischen Typs.

ArrayList<Integer> al = new ArrayList<Integer>()
al.add("Hallo"); // Compilerfehler

Auf diese Weise wird die Instanz der ArrayList ausschließlich zur Speicherung von Integer reserviert und der Versuch, wie oben etwa einen String abzulegen, wird bereits zur Kompilationszeit als Fehler registriert.

Wir haben es hier also mit einer sehr sinnvollen Verschiebung der Überprüfung der Typsicherheit von der JVM hin zum Compiler zu tun: Während der Compiler einen eventuellen Fehler vor Erzeugung des Bytecodes erkennt und die Kompilation ggfs. verweigert, bestünde ohne Typprüfung durch die Parametrisierung die Gefahr des Absturzes der JVM zur Laufzeit des Programms.