Softwaretechnologie, © Prof. Uwe Aßmann
Technische Universität Dresden, Fakultät Informatik 1
12) Generische Datenstrukturen
Prof. Dr. Uwe Aßmann Lehrstuhl Softwaretechnologie Fakultät für Informatik
TU Dresden Version 11-0.1, 07.05.11
Prof. U. Aßmann, Softwaretechnologie 2
Empfohlene Literatur
►
http://download.oracle.com/javase/6/docs/
►
Tutorials http://download.oracle.com/javase/tutorial/
►
Generics Tutorial:
http://download.oracle.com/javase/tutorial/extra/generics/index.html
2 Trends in der Softwareentwicklung
►
Rapid Application Development (RAD)
■
Schneller viel Code schreiben
■
Typisierung weglassen
♦ Bei den Assoziationen
♦ Beim Programmieren gegen Schnittstellen
■
Ev. dynamische Typisierung, damit Fehler zur Laufzeit identifiziert werden können
■
Mächtige Operationen, die schnell zu schreiben sind
►
Safe Application Development (SAD)
■
Guten, stabilen, wartbaren Code schreiben
■
Statische Typisierung, damit der Übersetzer viele Fehler entdeckt
■
Mehr Entwurfswissen aus dem Entwurf in die Implementierung übertragen
■
Aus der Definition einer Datenstruktur können Bedingungen für ihre Anwendung abgeleitet werden
►
Gradual Typing
■
Typen werden Schritt für Schritt annotiert
Zeit
Zuverlässigkeit groovy
Java Java 1.6
Bsp.: Elemente einer Hierarchie
Formular
Bestellung Lieferschein Rechnung
Prof. U. Aßmann, Softwaretechnologie 5
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.5 nicht durch den Übersetzer entdeckt werden
List listOfRechnung = new ArrayList();
Rechnung rechnung = new Rechnung();
listOfRechnung.add(rechnung);
Bestellung best = new Bestellung();
listOfRechnung.add(best);
for (int i = 0; i < listOfRechnung.size(); i++) { rechnung = (Rechnung)listOfRechnung.get(i);
}
List listOfRechnung = new ArrayList();
Rechnung rechnung = new Rechnung();
listOfRechnung.add(rechnung);
Bestellung best = new Bestellung();
listOfRechnung.add(best);
for (int i = 0; i < listOfRechnung.size(); i++) { rechnung = (Rechnung)listOfRechnung.get(i);
}
Programmierfehler!
Laufzeitfehler!!
Prof. U. Aßmann, Softwaretechnologie 6
Problem 2 ungetypter Schnittstellen: Unnötige Casts
►
Bei der Dekonstruktion von Collections müssen unnötig Casts spezifiziert werden
►
Typisierte Collections erhöhen die Lesbarkeit, da sie mehr Information geben
List listOfRechnung = new ArrayList();
Rechnung rechnung = new Rechnung();
listOfRechnung.add(rechnung);
Rechnung rechnung2 = new Rechnung();
listOfRechnung.add(rechnung2);
for (int i = 0; i < listOfRechnung.size(); i++) { rechnung = (Rechnung)listOfRechnung.get(i);
}
List listOfRechnung = new ArrayList();
Rechnung rechnung = new Rechnung();
listOfRechnung.add(rechnung);
Rechnung rechnung2 = new Rechnung();
listOfRechnung.add(rechnung2);
for (int i = 0; i < listOfRechnung.size(); i++) { rechnung = (Rechnung)listOfRechnung.get(i);
}
Diesmal ok
Cast nötig, obwohl alles Rechnungen
Abhilfe: Generische Klassen
►
In UML
Eine generische Klasse ist eine Klassenschablone, die mit einem Typparameter P versehen ist.
Eine generische Klasse ist eine Klassenschablone, die mit einem Typparameter P versehen ist.
►
In Java
■
Sprachregelung: “Container of P”
P
class Container<P> { P content[];
}
Container
content P
Generische Datentypen in der Collection- Hierarchie
►
Die generische Collection-Hierarchie (seit Java 1.5)
■
E: Element, K: Key, V: Value
E Collection
E List
E Set
E Queue
K,V Map
E K,V SortedSet
Blocking E
Prof. U. Aßmann, Softwaretechnologie 9
Instanz der Generischen Hierarchie
►
Darf man Rechnungen, Bestellungen und Lieferscheine in diese Collections stecken?
Ja.
Collection
<Formular>
List
<Formular> Set
<Formular>
Queue
<Formular> Map
<Nr,Formular>
SortedMap
<Nr,Formular>
SortedSet
<Formular>
Blocking Queue
<Formular>
E Collection
E List
E Set
E Queue
K,V Map
K,V SortedMap E
SortedSet
Blocking E Queue
<<instantiates>>
Prof. U. Aßmann, Softwaretechnologie 10
Probleme gelöst
►
Bei der Konstruktion von Collections werden jetzt Äpfel 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();
listOfRechnung.add(best);
for (int i = 0; i < listOfRechnung.size(); i++) { rechnung = listOfRechnung.get(i);
}
List<Rechnung> listOfRechnung = new ArrayList<Rechnung>();
Rechnung rechnung = new Rechnung();
listOfRechnung.add(rechnung);
Bestellung best = new Bestellung();
listOfRechnung.add(best);
for (int i = 0; i < listOfRechnung.size(); i++) { rechnung = listOfRechnung.get(i);
}
Compilerfehler
Kein Cast mehr nötig
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);
for (int jahr = 0; jahr < archiv.size(); jahr++) { listOfRechnung = archiv.getIndex(jahr);
for (int i = 0; i < listOfRechnung.size(); i++) { rechnung = listOfRechnung.getIndex(i);
} }
// 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);
for (int jahr = 0; jahr < archiv.size(); jahr++) { listOfRechnung = archiv.getIndex(jahr);
for (int i = 0; i < listOfRechnung.size(); i++) { rechnung = listOfRechnung.getIndex(i);
} }
funktioniert
Übersetzungs- Fehler
Benutzung von getypten und ungetypten Schnittstellen
►
.. ist ab 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);
for (int jahr = 0; jahr < archiv.size(); jahr++) { listOfRechnung = archiv.getIndex(jahr);
for (int i = 0; i < listOfRechnung.size(); i++) { rechnung = (Rechnung)listOfRechnung.getIndex(i);
} }
// 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);
for (int jahr = 0; jahr < archiv.size(); jahr++) { listOfRechnung = archiv.getIndex(jahr);
for (int i = 0; i < listOfRechnung.size(); i++) { rechnung = (Rechnung)listOfRechnung.getIndex(i);
} }
funktioniert
Übersetzt auch, aber Laufzeitfehler
beim Cast...
Prof. U. Aßmann, Softwaretechnologie 13
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;
}
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 14
Implementierungsmuster Command:
Generische Methoden als Funktionale Objekte
►
Funktionalobjekte können Berechnungen kapseln und später ausführen (laziness) (Entwurfsmuster Command)
■
Es gibt eine Standard-Funktion in der Klasse des Funktionalobjektes, das die Berechnung ausführt (Standard-Name, z.B. execute() oder doIt())
►
Zur Laufzeit kann man das Funktionalobjekt mit Parametern versehen, herumreichen, und zum Schluss ausführen
Ein Funktionalobjekt (Kommandoobjekt) ist ein Objekt, das eine Funktion darstellt (reifiziert).
Ein Funktionalobjekt (Kommandoobjekt) ist ein Objekt, das eine Funktion darstellt (reifiziert).
// A functional object that is like a constant interface NullaryOpCommand { void execute(); } // A functional object that takes one parameter interface UnaryOpCommand<P> { P execute(P p1); } // A functional object that operates on two parameters interface BinOp<P> { P execute(P p1, P p2); }
// A functional object that is like a constant interface NullaryOpCommand { void execute(); } // A functional object that takes one parameter interface UnaryOpCommand<P> { P execute(P p1); } // A functional object that operates on two parameters interface BinOp<P> { P execute(P p1, P p2); }
Generische Methoden als Funktionale Objekte
►
Anwendung: Akkumulatoren und andere generische Listenoperationen
// 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,E> implements Functional<C,BinOp<E>> { E curSum; E element; BinOp<E> binaryOperation;
public E compute(C coll) {
for (int i = 0; i < coll.size(); i++) { element = coll.getIndex(i);
curSum = binaryOperation.execute(curSum,element);
}
return curSum;
}}
// 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,E> implements Functional<C,BinOp<E>> { E curSum; E element; BinOp<E> binaryOperation;
public E compute(C coll) {
for (int i = 0; i < coll.size(); i++) { element = coll.getIndex(i);
curSum = binaryOperation.execute(curSum,element);
}
return curSum;
} } P BinOp
Collection
<P>
Functional
C,E Accumulate
B
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 17
Was haben wir gelernt
►
Static vs. dynamic vs. gradual vs. no typing
►
Generische Datenstrukturen sind Klassenschablonen mit Typ- Parameter
■
Feinere statische Typisierung möglich
►
Generische Collections besitzen den Element-Typ als Typ-Parameter
■
Element-Typ verfeinert Object
■
Weniger Casts, mehr Typsicherheit
►
Implementierungsmuster Command
Prof. U. Aßmann, Softwaretechnologie 18