570 likes | 663 Views
Generizität in Java und C#. Riad Djemili (djemili@inf.fu-berlin.de) Seminar Objektorientierte Programmiersprachen Prof. Dr.-Ing. Klaus-Peter Löhr FU-Berlin WS 03/04. Übersicht. Motivation Polymorphie Probleme Generizität Grundlagen (Eiffel, C++) Java C#. Motivation. Polymorphie.
E N D
Generizitätin Java und C# Riad Djemili (djemili@inf.fu-berlin.de) Seminar Objektorientierte Programmiersprachen Prof. Dr.-Ing. Klaus-Peter Löhr FU-Berlin WS 03/04
Übersicht • Motivation • Polymorphie • Probleme • Generizität • Grundlagen (Eiffel, C++) • Java • C#
Polymorphie • PolymorphieEigenschaft von Variablen, Objekte unterschiedlichen Typs speichern zu können. Variablen können Objekte zugewiesen werden, die vom gleichen Typen wie diese Variable oder von einem abgeleiteten Typen sind.List list = new LinkedList(); • Statischer TypIn der Variablendeklaration festgelegter Datentyp. • Dynamischer TypBeim Erzeugen des Objekts festgelegter Typ.
Probleme • Mächtiges Paradigma, aber nicht perfekt! • Statischer Datentyp ist fest und kann für eigene Anwendungen nicht leicht angepasst werden. • Klassische Lösung: • Nutzung des niedrigsten gemeinsamen Typen der Klassenhiarchie. • Spezialiserte Implementationen mit unterschiedlichen Typen.
Ansatz 1:Nutzung von Vaterobjekten • Beispiel Java: Listeclass MyList { boolean add(Object o) {..} Object get(int index) {..}}.. • //associates Integer-Objects MyList ints = new MyList(); ints.add(new Integer(4)); Integer foo = (String)ints.get(0); • Unsichere Konvention, statt expliziter Sprachunterstützung. • Typwandlungs-Laufzeitfehler, statt Übersetzungsfehler. • Teure Typwandlungen zu Basisklasse und zurück. • Nicht alle Programmiersprachen kennen gemeinsame Vaterobjekte.
Ansatz 2:Spezialisierte Implementationen • Beispiel Java: Math-Methodenpublic static double abs(double a) {..}public static float abs(float a) {..} public static int abs(int a) {..}public static long abs(long a) {..} • Methodenüberladen (gleiche Methodennamen mit unterschiedlichen Signaturen), um verschiedene Datentypen zu unterstützen. • Effizient, aber aufgeblähter und unübersichtlicher Code!
Lösung: Generizität (I) Polymorphie • Möglichkeit nicht nur dynamischen Typ, sondern auch statischen Typ zu variieren. Ad hoc – Polymorphie (überladen) Universelle Polymorphie Inklusions-Polymorphie (überschreiben) Parametrische Polymorphie (Generizität)
Lösung: Generizität (II) • Ziele • Höhere Typsicherheit, durch Vermeidung von unsicheren Typumwandlungs-Laufzeitfehlern. • Wiederverwendbarer Code. • Bessere Lesbarkeit, durch expliziter Syntax, statt impliziter Konvention. • Effizienz, da keine unnötigen Typwandlungen.
Klassen Generizität • Klassen werden mit formalen Typparametern deklariert. • Tatsächliche Datentypen werden als Parameter übergeben. Ungenerischer Stack in Eiffel Generischer Stack in Eiffel • class STACK [T] .. feature push ( elem: T) is do .. end top: Tis do .. end end • Anwendung:intstack : STACK[INTEGER];intstack.push(3); • class STACK .. feature push ( elem: INTEGER) is do .. end top: INTEGERis do .. end end • Anwendung:intstack : STACK;intstack.push(3);
Mehrere Parametertypen • Ausserdem mehrere generische Parametertypen möglich. • Beispiel: Ein generisches Paar in Eiffelclass PAIR[T, U]feature {NONE} // private first : T; second : U;end • Anwendung:birthdate : PAIR[STRING,DATE];
Methoden Generizität • Generische Typen existieren auch für Funktionen! • Beispiel: Vertausche-Operation in C++template<class T>void swap(T& x, T& y) {T temp = x; x = y; y = temp;} • Anwendung int i,j; swap<int> (i,j); //a swap for intchar i,j; swap<char>(i,j); //a swap for char
Ohne explizite Datentypangabe. Methoden Generizität (II) • Im Gegensatz zu generischen Klassen können Typparameter bei Methoden implizit aus Argumenten abgeleitet werden!Beispiel in C++: int i,j; swap(i,j); // a swap for intfloat i,j; swap(i,j); // a swap for float
Eingeschränkte Generizität • Typeinschränkungen mittels klassischer Polymorphie:bool pos(Comparable foo){return foo.compareTo(x);} • Problem: Wie können wir ähnlich sichere Annahmen für generische Typen machen? • Lösung: Constrained Genericty. class SORTED_LIST [T->COMPARABLE] ..end • Erzwingt in Eiffel, dass T Unterklasse von Comparable sein muss, ansonsten Übersetzungsfehler.
Übersicht • Seit 1995 von Sun Microsystems entwickelt. • Stark objektorientiert. • Umfangreiche Klassenbibliothek. • Automatische Speicherverwaltung. • Plattformunabhängig. • Aus „Generic Java“ entwickelt und offiziell ab Java 1.5 (Sommer 2004, Codename: Tiger) unterstützt.
Ziele der Java-Implementation • Vollständige Ersetzung der aktuellen Java API durch generische Version. • „Retrofitting“ alten Codes. • Vollständige Rückwärtskompabilität. • Alter Code soll ohne Änderung ausführbar bleiben.
Parametertypen • Erlaubte Typarten: • Klassen sind als Parametertypen erlaubt. • Primitive Typen sind nicht erlaubt. Unteranderem da für sie kein einheitliches Typsystem existiert. Sie müssen jeweils (uneffizient) in ihre Wrapper-Klassen gekapselt werden. z.B. int → Integer, boolean → Boolean • Übrigens automatisches „Casting“ (Autoboxing) auch ab Java 1.5. • Ausserdem mehrere Typparameter erlaubt.
Klassen Generizität • Beispiel: Die neue java.util.Stack Klasseclass Stack<E> extends Vector<E> { public E push(E item) {..} public synchronized E pop() {..} public synchronized E peek() {..} public boolean empty() {..} public synchronized int search(Object o) {..}} • Anwendung:Stack<String> foo = new Stack<String>();
Methoden Generizität • Methoden-Generics unterstützt. • Typangabe (unintuitiverweise) vor Methodenname! Begründung ist leichteres Parsing. Beispiel: Eine vertausche-Operationstatic <Elem> void swap(Elem[] array, int x, int y) { Elem temp = array[x]; array[x] = array[y]; array[y] = temp;} • Aufrufe auch mit impliziter Typangabe. swap(ints, 1, 3)sort(strings)<Integer>swap(ints, 1, 3)<String>sort(strings)
Bounds • Einschränkung (bounding) der Typen durch • maximal eine Oberklasse und/oder • beliebiger Anzahl von Interfaces. • Beispiel: Sortierte Listeclass SortedList<T extends Entry implements Comparable> {..}
Übersetzung (I) Java Implementierung von Generizitäts-Erweiterungen auf Ebene der Übersetzung. Übersetzung(Bytecode) Ausführung (JVM) Windows .. Mobile Linux Plattformen
Übersetzung (II) • Oberster Grundsatz:Die Java Virtual Machine wird nicht verändert! • Homogener Vorgang (für jeden generischen Typen wird einmalig neuer Code generiert) • Generische Datentypen werden durch untere Grenze ersetzt (Erasures). • Casting geschieht bei Attributzugriffen und Methodenrückgaben. • Einsetzung von Bückenmethoden bei kovarianter Vererbung. (siehe später) • Vorgang fast analog zum „manuellen“ Vorgang bisher.
Übersetzung (III) Übersetzung in JVM – konformen Code. class Cell<A> { A value; A getValue();} .. String f(Cell<String> cell){ return cell.value;} class Cell {Object value; Object getValue();} .. String f(Cell cell){ return (String)cell.value;} Klasse Cell wird nur einmal generiert und kann für alle Parametertypen wiederverwendet werden.
Legacy-Code (I) • Java API soll mit generischen Typen aktualisiert werden.Stack s = new Stack(); geht aber immernoch (sogenannte „Raw Classes“)! Ohne Angabe eines Typparameters wird implizit niedrigste Typschranke angenommen. Hier also:Stack<Object> s = new Stack<Object>(); • Ermöglicht „Rückwärts-Kompabilität“, dh. alter Code kann ohne Änderung weiter mit „retrofitted“ API Klassen arbeiten. • Evtl. auch „Vorwärts-Kompabilität“ denkbar.
Legacy-Code (II) • Zuweisungen • ErlaubtStack s = new Stack<Integer>();Übersetzer-Warnungen, bei • Methodenaufrufem mit veränderten Argumenttypen. • Attributzugriffe mit veränderten Typen.s.push(new Integer(5)); //compile-warning • Erlaubt, aber „deprecation“-Warnung, da unsicher.Stack<Integer> s = new Stack();
Kovarianz (I) • Bisher: Invariante Rückgabetypen • Rückgabetyp einer Methode muss identisch sein mit der überschriebenen Methode. • Jetzt: Kovariante Rückgabetypen • Rückgabetyp einer Methode muss Untertyp sein für alle Methoden, die sie überschreibt. • Kovarianz wird durch Brückenmethoden realisiert.
Kovarianz (II) class A<T> { T something() { .. }}class B<T implements Comparable> extends A<T> { T something() { .. }} class A { Object something() { .. }}class B extends A { Comparable something() { .. } Object something/*2*/() { return something() } } JVM unterstützt intern auch Methoden mit Signaturen, die sich nur im Rückgabetypen unterscheiden.
Vererbung (I) • Unintuitive Typbeziehung • LinkedList<String> ist Untertyp von List<String>. • List<String> ist aber nicht Untertyp von List<Object>.
Typfehler! Vererbung (II) • Naive generische Implementation von Interface Collection:interface Collection<E> { boolean addAll(Collection<E> c); boolean containsAll(Collection<E> c); } • Nachteil: • Aufgrund von fehlerender Typbeziehung kann die generische Methode nur eigenen Typen aufnehmen.Collection<Number> = new Collection<Number> col;Collection<Integer> = new Collection<Integer> ints;col.addAll(ints);
Vererbung (III) • Bessere generische Implementation erlaubt kovariante Argumente: interface Collection<E> { <T extends E> boolean addAll(Collection<T> c); <T> boolean containsAll(Collection<T> c); } • Auch wenn aktueller Typparameter bei Methodenaufruf implizit übergeben werden kann:Unleserliche und umständliche Notation für häufiges Programmierziel.
Vererbung (IV) • Vorangetrieben durch „Variant Generic Java” WildCard-Syntax. • Ermöglicht bessere Lesbarkeit durch anonyme Typparameter.interface Collection<E> { boolean addAll(Collection<? extends E> c); boolean containsAll(Collection<?> c);} • Ermöglicht auch Kontravarianz.interface Collection<E> { boolean addAll(Collection<? super E> c); boolean containsAll(Collection<?> c);} • Aber, was trotzdem nicht geht:List<? extends Number> = new List<Integer>;
Exceptions • Nicht vollständig unterstützt. • Generics dürfen nicht von java.lang.Throwable ableiten. • Typvariablen sind • erlaubt in throws – Anweisung. • nicht erlaubt in catch – Anweisung.
Instanziieren von Parametertypen • Aufgrund von Erasures ist es nicht möglich Parametertypen zu instanziieren.class Singleton<T> { private T instance; public T getInstance() { if (instance == null)instance = new T(); return instance; }} Nicht erlaubt!
Reflection • Generische Klassen als Typen bekannt, allerdings keine Kenntnis über aktuelle Typparameter zur Laufzeit, da diese beim Übersetzen entfernt werden (erasures). class SortedSet<T extends Comparable<T>> { public SortedSet() {if (T.class == Date.class) { .. } else if (T.class == Integer.class) { .. } }} Kann nicht funktionieren, da T.class hier immer vom Typ Comparable ist.
Schwächen von Erasures • Unerwartete Fehler aufgrund von gleichen Erasures • class Tool { public void do(Collection<Integer>) {..} public void do(Collection<String>) {..}} • class C<A> { A id(A x) {..}}interface I<A> { A id(A x);}class D extends C<String> implements I<Integer> { String id(String x) {..} Integer id(Integer x) {..}}
Zusammenfassung • Vorteil • Leichter Übergang zwischen generischem und ungenerischem Code. • Keine neue konkurrierende Java API. • Anonyme Typparameter (Wildcards). • Nachteil • Keine primitiven Datentypen erlaubt. • Uneffizient, da up/down - casting intern immernoch nötig. • Kein Wissen über aktuelle generische Typparameter. • Typparameter können nicht instanziiert werden. • Verschiedene Restriktionen im Zusammenhang mit Erasures
Übersicht • Seit Februar 2001 von Microsoft entwickelt. • Stark objektorientiert. • Umfangreiche Klassenbibliothek. • Automatische Speicherverwaltung. • Teil des .NET Frameworks • Bald (Mitte/Ende 2004!?) auch Generics.
Ziel der .Net Implementation • Saubere vollständige Implementation von Generics. • Einführung neuer Intermediate Language Typen. • Einführung neuer generischer Klassen in das .NET Framework. • Aktuelles nicht-generisches .NET Framework bleibt erhalten.
Parametertypen • Typarten: • Klassen sind als Parametertypen erlaubt. • Primitive Typen sind erlaubt, da alle Typen (Referenz- und Datentypen) einen gemeinsamen Obertypen haben. • Auch mehrere generische Typen erlaubt.
Klassen Generizität • Beispiel: Eine sortierte Listeclass SortedList<EntryType> where EntryType : IComparable{ public void insert(EntryType item) {..} public void remove(EntryType item) {..} public bool contains(EntryType item) {..} public bool empty() {..}} • Anwendung:SortedList<int> ss = new SortedList<int>(); • Ausserdem Unterstützung für Structs und Delegates.
Methoden Generizität • Methoden-Generics unterstützt. Typangabe intuitiverweise hinter Methodenname! Beispiel: swapstatic void swap<Elem>(Elem[] array, int x, int y) { Elem temp = array[x]; array[x] = array[y]; array[y] = temp; } • Aufrufe auch mit impliziter Typangabe. swap(ints, 1, 3)sort(strings)swap<int>(ints, 1, 3)sort<string>(strings)
Constraining • Einschränkung (analog zu Java) der Typen durch • maximal eine Oberklasse und/oder • beliebiger Anzahl von Interfaces. • Beispiel: Zwei eingeschränkte Typparameter. pulic class MyList<K,T> where K : Icomparable<K>, IEnumerable where T : Customer { .. } • Notation umständlicher als bei Java!?
Übersetzung (I) Sprachen VB .NET .. Java# C# Übersetzung(MIL) Implementierung von Generizitäts-Erweiterungen auf Ebene der Ausführung. Ausführung (CLR) Windows .. Mobile (Linux) Plattformen
Übersetzung (II) • Typabhängige Übersetzung: • Für ReferenceTypes (Klassen)wird einmal generierter gemeinsamer Code wiederverwendet (homogener Vorgang). • Für ValueTypes (primitive Typen)wird bei jeder ersten Benutzung spezieller Code generiert (heterogener Vorgang).
Legacy-Code (I) • .NET API erhält zusätzliche generische Klassen. • Ausmass abgesehen von Collections Namespace noch unbekannt. • „Rückwärts-Kompabilität“ wird durch Beibehaltung der alten Klassen gewährleistet. • Mischung von generischem und ungenerischem Code nur schwer.
Typenvarianz • Nur Invariante Rückgabetypen • Rückgabetyp einer Methode muss identisch sein mit der überschriebenen Methode. • Keine Wildcard-Syntax. • Keine Kontravarianz.