Generische Datenstrukturen Prof. Dr. rer. nat. habil. Uwe Aßmann Lehrstuhl Softwaretechnologie Fakultät für Informatik TU Dresden Softwaretechnologie, © Prof. Uwe Aßmann 1 2 Trends in der Softwareentwicklung ► Rapid Application Development (RAD) ■ ■ Schneller viel Code schreiben Typisierung weglassen Zeit pike ♦ Bei den Assoziationen ♦ Beim Programmieren gegen Schnittstellen ■ ► Mächtige Operationen, die schnell zu schreiben sind Java Java 1.5 Safe Application Development (SAD) ■ ■ ■ Zuverlässigkeit Guten Code schreiben Typisierung, damit der Übersetzer viele Fehler entdeckt (statische Typisierung) Mehr Entwurfswissen aus dem Entwurf in die Implementierung übertragen ♦ Typisierung der Assoziationen ■ Aus der Definition einer Datenstruktur können Bedingungen für ihre Anwendung abgeleitet werden Prof. U. Aßmann, Softwaretechnologie 2 Elemente einer Hierarchie Formular Bestellung Lieferschein Rechnung Prof. U. Aßmann, Softwaretechnologie 3 Problem 1 ungetypter Schnittstellen: Laufzeitfehler ► ► Bei der Konstruktion von Collections werden oft Fehler programmiert, die bei der Dekonstruktion zu Laufzeitfehlern führen Kann in Java < 1.4 nicht durch den Übersetzer entdeckt werden List listOfRechnung = new ArrayList(); Rechnung rechnung = new Rechnung(); listOfRechnung.add(rechnung); Bestellung best = new Bestellung(); listOfRechnung.add(best); Programmierfehler! for (int i = 0; i < listOfRechnung.size(); i++) { ((Rechnung)rechnung) = listOfRechnung.get(i); } Laufzeitfehler!! Prof. U. Aßmann, Softwaretechnologie 4 Problem 2 ungetypter Schnittstellen: Unnötige Casts ► Bei der Dekonstruktion von Collections müssen unnötig Casts spezifiziert werden List listOfRechnung = new ArrayList(); Rechnung rechnung = new Rechnung(); listOfRechnung.add(rechnung); Rechnung rechnung2 = new Rechnung(); listOfRechnung.add(rechnung2); Diesmal ok for (int i = 0; i < listOfRechnung.size(); i++) { ((Rechnung)rechnung) = listOfRechnung.get(i); } Cast nötig, obwohl alles Rechnungen Prof. U. Aßmann, Softwaretechnologie 5 Abhilfe: Generische Klassen Eine generische Klasse ist eine Klassenschablone, die mit einem Typparameter P versehen ist. ► In UML ► In Java ■ Sprachregelung: “Array of T” P Container content P class Container<P> { P content[]; } Prof. U. Aßmann, Softwaretechnologie 6 Generische Datentypen in der Collection-Hierarchie ► Die neue, generische Hierarchie (seit Java 1.5) E Collection E List E Set Queue E SortedSet E Blocking Queue K,V Map E K,V SortedMap Prof. U. Aßmann, Softwaretechnologie 7 Instanz der Generischen Hierarchie E Collection E E List Collection <Formular> Set <<instantiates> > Queue E SortedSet List <Formular> Set <Formular> SortedSet <Formular> Queue <Formular> Blocking Queue <Formular> E Map <Nr,Formular> Blocking Queue ► K,V Map K,V E SortedMap Darf man Rechnungen, Bestellungen und Lieferscheine in diese Collections stecken? Ja. SortedMap <Nr,Formular> Prof. U. Aßmann, Softwaretechnologie 8 Probleme gelöst ► ► Bei der Konstruktion von Collections werden jetzt Äpfel werden von Birnen unterschieden Casts sind nicht nötig, der Übersetzer kennt den feineren Typ List<Rechnung> listOfRechnung = new ArrayList<Rechnung>(); Rechnung rechnung = new Rechnung(); listOfRechnung.add(rechnung); Bestellung best = new Bestellung(); Compilerfehler listOfRechnung.add(best); for (int i = 0; i < listOfRechnung.size(); i++) { rechnung = listOfRechnung.get(i); } Kein Cast mehr nötig Prof. U. Aßmann, Softwaretechnologie 9 Generizität funktioniert auch geschachtelt // Das Archiv fasst alle Rechnungen aller bisherigen Jahrgänge zusammen List<List<Rechnung>> archiv = new ArrayList<List<Rechnung>>(); // listOfRechnung fasst die Rechnungen des aktuellen Jahres zusammen List<Rechnung> listOfRechnung = new ArrayList<Rechnung>(); archiv.add(listOfRechnung); Rechnung rechnung = new Rechnung(); archiv.getIndex(0).add(rechnung); Bestellung best = new Bestellung(); archiv.getIndex(0).add(best); funktioniert ÜbersetzungsFehler for (int jahr = 0; jahr < archiv.size(); jahr++) { listOfRechnung = archiv.getIndex(jahr); for (int i = 0; i < listOfRechnung.size(); i++) { rechnung = listOfRechnung.getIndex(i); } } Prof. U. Aßmann, Softwaretechnologie 10 Benutzung von getypten und ungetypten Schnittstellen ► .. ist in Java 1.5 ohne Probleme nebeneinander möglich // Das Archiv fasst alle Rechnungen aller bisherigen Jahrgänge zusammen List<List<Rechnung>> archiv = new ArrayList<List<Rechnung>>(); // listOfRechnung fasst die Rechnungen des aktuellen Jahres zusammen List listOfRechnung = new ArrayList(); archiv.add(listOfRechnung); Rechnung rechnung = new Rechnung(); archiv.getIndex(0).add(rechnung); Bestellung best = new Bestellung(); archiv.getIndex(0).add(best); funktioniert Übersetzt auch, for (int jahr = 0; jahr < archiv.size(); jahr++) { aber Laufzeitfehler listOfRechnung = archiv.getIndex(jahr); beim Cast... } for (int i = 0; i < listOfRechnung.size(); i++) { rechnung = (Rechnung)listOfRechnung.getIndex(i); } Prof. U. Aßmann, Softwaretechnologie 11 Typschranken generischer Parameter (type bounds) ► Beispiel: Comparable<E> als Return-typ in der Collections-Klasse sichert zu, dass die Methode compareTo() existiert class Collections { /** minimum function for a Collection. Return value is typed * with a generic type with a type bound */ public static <E extends Comparable<E>> min(Collection<E> ce) { Iterator<E> iter = ce.iterator(); E curMin = iter.next; if (curMin == null) return curMin; for (E element = curMin; iter.hasNext(), element = iter.next) { if (element.compareTo(curMin) < 0) { curMin = element; } } return curMin; } Prof. U. Aßmann, Softwaretechnologie 12 Generische Methoden als Funktionale Objekte Ein Funktionalobjekt ist ein Objekt, das eine Funktion darstellt (reifiziert). ► Funktionalobjekte können Berechnungen kapseln und später ausführen (laziness) ■ Es gibt eine Funktion in der Klasse des Funktionalobjektes, dass die Berechnung ausführt ♦ Generischer Name, z.B. execute() oder doIt() ► Zur Laufzeit kann man ■ ■ ■ das Funktionalobjekt mit Parametern versehen Herumreichen Und zum Schluss ausführen Prof. U. Aßmann, Softwaretechnologie 13 Generische Methoden als Funktionale Objekte ► Anwendung: Akkumulatoren und andere generische Listenoperationen P BinOp Collection <P> B Functional E Accumulate // A functional object that adds, once invoked interface BinOp<P> { P execute(P p1, P p2) } // an interface for a collection of binary operation on // collections interface Functional<Collection<P>,B extends BinOp<P>> { P compute(Collection<P> p); } class Accumulate<C,P> implements Functional<C,P> { P curSum; P element; B binaryOperation; public P compute(Collection<P> c) { for (int i = 0; i < c.size(); i++) { element = c.getIndex(i); binaryOperation.execute(curSum,p); } return curSum; } } Prof. U. Aßmann, Softwaretechnologie 14 Unterschiede zu C++ ► In Java: einmalige Übersetzung des generischen Datentyps ■ ■ ► ► Verliert etwas Effizienz, da der Übersetzer alle Typinformation im generierten Code vergisst und nicht ausnutzt z.B. sind alle Instanzen mit unboxed objects als boxed objects realisiert C++ bietet Code-Templates (snippets, fragments) an, mit denen man mehr parameterisieren kann, z.B. Methoden In C++ können Templateparameter Variablen umbenennen: template class C <class T> { T attribute<T> } Templateparameter können Variablen umbenennen Prof. U. Aßmann, Softwaretechnologie 15 End Prof. U. Aßmann, Softwaretechnologie 16