Vorlesung Informatik 2
Fachhochschule für Technik Esslingen Studiengang Wirtschaftsinformatik
Generics
Dr. rer. nat. Andreas Rau
???
???
Dieser Foliensatz erklärt das mit Java 5 eingeführte Sprachfeature "Generics".
Erinnern Sie sich noch an die Warnung
Note: Foo.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.
In diesem Teil der Vorlesung erfahren Sie, wo diese Warnung herkommt.
Inhalt
Hintergrund
Mit Hilfe der Klasse Object und der Vererbungshierarchie darunter ist es möglich, universelle Algorithmen und Datenstrukturen für vielfältige Objekte zu implementieren. Allerdings geht dabei die Typsicherheit teilweise verloren. Eine Einschränkung auf einen bestimmten Typ ist nur durch zusätzliche Schreibarbeit (Prüfungen, Kapselung) möglich. Um solche Nachteile zu umgehen, bieten viele mit Java vergleichbare Programmiersprachen Mechanismen wie z.B. Templates an.
Generics(1)
Bedeutung
Ein Template ist eine Vorlage oder Schablone für eine Familie von Algorithmen bzw. Datenstrukturen. Dabei steht die Struktur schon exakt fest. Lediglich einige Typangaben sind mit Hilfe von Platzhaltern formuliert und werden erst bei der Verwendung der Vorlage durch einen konkreten Typ ersetzt. Praktisch bedeutet dies, dass der Compiler „die Vorlage unter Verwendung des angegebenen Typs nochmal abschreibt“
und übersetzt.
In Java heißt der entsprechende Mechanismus Generics. Damit lassen sich keine Templates sondern sogenannte parametrisierte Typen definieren. Diese werden nicht wie Templates mehrfach instantiiert sondern nur zur Übersetzungszeit mit zusätzlichen Prüfungen überwacht.
Das Prinzip ist ähnlich, jedoch wird auf diese Art und Weise wesentlich weniger Code erzeugt.
Generics(2)
Quellcode mit Typparameter
Generics(2a)
Deklaration mit Typparameter Quellcode mit
Typparameter Ausprägung
für String
Quellcode für Basistyp Object
Template, in Sprachen ohne Basistyp
String Adapter
String Adapter
Generics, in Sprachen mit Basistyp
In den Adaptern steckt "normaler"
Java Code. Die Collection arbeitet im class-File also mit Object. Dies nennt man type-erasure.
Als syntaktischer Zucker werden solche Sprachelemente bezeichnet, die dem Programmierer keine (grundlegend) neuen Möglichkeiten bieten, sondern die vorhandenen Möglichkeiten auf eine kürzere, bequemere oder sicherere Art verpacken bzw. zugänglich machen.
Eine Vielzahl der Neuerungen in Java 5 bestand aus solchen syntaktischen Zucker, z.B. enums, for-each und eben die Generics.
Gleichwohl war diese Version ein großer Schritt nach vorne, da die Programme durch die neuen Elemente kürzer und klarer werden und die Programmierer so mehr Zeit für wichtigere Problemstellungen haben
Was ist syntaktischer Zucker?
Beispiel (ohne Generics)
// (1) Keine Prüfung beim Befüllen List liste = new ArrayList();
liste.add( "Test");
liste.add( new Integer(1)); // möglicherweise nicht gewollt!
// ...
// (2) Zugriff erfordert Cast (kann schiefgehen) String s1 = (String)liste.get(0);
String s2 = (String)liste.get(1); // Laufzeitfehler // (3) Schleifen sind "unbequem"
for ( Object o : liste) { String s = (String)o;
// ...
}
Iterator iter = liste.iterator();
while ( iter.hasNext()) {
String s = (String)iter.next();
}
Generics(2a)
unchecked or unsafe operations
Beispiel (mit Generics)
// (1) Prüfung beim Befüllen
List<String> liste = new ArrayList<String>();
liste.add( "Test");
liste.add( new Integer(1)); // Compile-Fehler // ...
// (2) Zugriff ohne Cast (klappt immer) String s1 = liste.get(0);
String s2 = liste.get(1);
// (3) Schleifen sind nicht mehr "unbequem"
for ( String s : liste) { // ...
}
Iterator<String> iter = liste.iterator();
while ( iter.hasNext()) {
Generics(2a)
Durch die Einführung der Generics gewinnt man also eine Menge an Sicherheit und spart (Schreib)arbeit ohne dabei Flexibilität zu verlieren.
siehe dazu auch
http://java.sun.com/developer/technicalArticles/J2SE/generics/
Die Hauptanwendung von Generics liegt bei den Collections oder allgemeiner ausgedrückt, bei Algorithmen die "Objekte bewegen ohne sie näher kennen zu müssen". Diese sind in der Klassenbibliothek an dem Typparameter erkennbar, z.B. ArrayList<E> oder HashMap<K,V> oder an Interfaces wie Comparator<T>.
Als Programmierer ist man daher meist nur Anwender von Collections. Es ist jedoch auch möglich, eigene generische Strukturen zu implementieren und dabei den Typparameter ggf. auch einzuschränken.
Generics(2c)
Implementierung einer generischen Klasse
Um eine generische Klasse zu schreiben, gibt man bei der Definition einen oder mehrere Platzhalter für Datentypen an. Diese werden bei der Deklaration(=Verwendung) durch reale Datentypen ersetzt.
Beispiel für Implementierung
public class Capsule<E> { // generische Klasse E value;
public Capsule( E value) { this.value = value;
}
public E getValue() { return value;
}}
Beispiel für Verwendung
Generics(3a)
Implementierung einer generischen Methode
Man kann auch einzelne Methoden generisch machen – auch in nicht generischen Klassen. Um eine generische Methode zu verwenden, kann man diese ohne Angabe eines Typparameters einfach aufrufen. Der Compiler ermittelt den Typ dann selbstständig aus den Parametern.
Beispiel
public class ListHelper { // erzeugt eine Liste mit Startwerten public static <T> List<T> create( int count, T value) {
List<T> list = new ArrayList<T>();
for ( int i=0; i<count; ++i) { list.add( value);
}
return list;
}}
List<String> sList = ListHelper.create( 10, "");
List<Integer> iList = ListHelper.create( 5, new Integer(0));
Generics(3b)
Rolle des Typparameters
Wie die Parameter einer Funktion, die als Platzhalter für die Beschreibung von Abläufen und Berechnungen verwendet werden (Man beschreibt anhand der Platzhalter, wie später die tatsächlichen Werte verwendet werden) dient der Typparameter als Platzhalter für einen (bewußt) noch unbekannten Typ bei der Beschreibung einer Klasse oder Methode (Man beschreibt anhand des Platzhalters, welche Werte welchen Typ haben).
Als Konvention gilt dabei, dass für den Typparameter der grossgeschriebene Anfangsbuchstabe seiner Bedeutung verwendet wird (E wie Element, K wie Key, V wie Value, T wie Typ, ...). Bei mehreren Typparametern kann es ggf. auch sinnvoll sein, diese Bedeutung zusätzlich im (Javadoc)Kommentar zu vermerken.
Generics(3c)
Typverträglichkeiten von generischen Collections
Für einfache Variablen gilt das Prinzip der Polymorphie.
Person p = new Student(); // geht
Bei Collections funktioniert dies nicht ganz so wie zunächst erwartet.
List<Person> = new ArrayList<Student>(); // geht nicht
Warum geht das nicht, obwohl Student doch von Person erbt? Ein Beispiel...
List<Student> ls = new ArrayList<Student>();
List<Person> lp = ls;
lp.add( new Person( "karl", "killer"));
Student s = ls.get(0); // Exception! Kein Student
Nach Zuweisung an eine Liste der Superklasse könnte man also eine Liste der Subklasse "verschmutzen" und so die Typsicherheit zerstören!
Generics(4)
Einschränkung des Typparameters
Mit Generics kann man noch viel mehr machen! So kann man z.B. die möglichen Typen bei eigenen Klassen durch sog. Bounds einschränken.
Beispiele: Visualisierung siehe die nächsten beiden Folien
Verwendung einer obere Schranke
public class Upper<T extends Person> { /* ... */ } // Upper Bound
Der Typparameter T muss mit einer Klasse belegt werden, die eine Person ist.
Verwendung einer untere Schranke
public class Lower<T super Student> { /* ... */ } // Lower Bound
Generics(4)
Generics(4a) Object
Thing Lifeform
Vehicle Person Animal
Worker Teilbaum
<T extends Person> Student
Generics(4b) Object
Thing Lifeform
Vehicle Person Animal
Pfad
Wildcards
Um Methoden mit generischen Parametern möglichst vielseitig einsetzen zu können kann man statt konkreten Typparametern auch sog. Wildcards verwenden. Dabei steht die Wildcard ? für den unbekannten Typ. Auch Wildcards können nach oben und unten beschränkt werden.
public void printCollection( Collection<?> c) { Iterator i = c.iterator();
for (k = 0; k < c.size(); k++) { System.out.println(i.next());
} }
Nachteil dabei ist, das nun in der Methode keine Elemente mehr hinzugefügt werden können, da der richtige Typ ja nun nicht mehr bekannt ist. Genauer:
Methoden, die ein T als Parameter erwarten sind verboten. Methoden, die den generischen Typ T zurückgeben, liefern nun ein Object (bzw. die Schranke).
siehe dazu auch
http://java.sun.com/docs/books/tutorial/extra/generics/wildcards.html