Generizität

Werbung
Generizität
in Java und C#
Riad Djemili ([email protected])
Seminar Objektorientierte Programmiersprachen
Prof. Dr.-Ing. Klaus-Peter Löhr
FU-Berlin WS 03/04
Übersicht
1.
Motivation
Polymorphie
2. Probleme
1.
2.
Generizität
Grundlagen (Eiffel, C++)
2. Java
3. C#
1.
Motivation
Polymorphie

Polymorphie
Eigenschaft 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 Typ
In der Variablendeklaration festgelegter Datentyp.

Dynamischer Typ
Beim 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: Liste
•
class MyList {
boolean add(Object o) {..}
Object get(int index) {..}
}
Unsichere Konvention,
statt expliziter
Sprachunterstützung.
•
TypwandlungsLaufzeitfehler, statt
Übersetzungsfehler.
•
Teure Typwandlungen zu
Basisklasse und zurück.
•
Nicht alle
Programmiersprachen
kennen gemeinsame
Vaterobjekte.
..

//associates Integer-Objects
MyList ints = new MyList();
ints.add(new Integer(4));
Integer foo = (String)ints.get(0);
Ansatz 2:
Spezialisierte Implementationen

Beispiel Java: Math-Methoden
public
public
public
public

static
static
static
static
double
float
int
long
abs(double a)
abs(float a)
abs(int a)
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
Universelle
Polymorphie
InklusionsPolymorphie
(überschreiben)

Ad hoc –
Polymorphie
(überladen)
Parametrische
Polymorphie
(Generizität)
Möglichkeit nicht nur dynamischen Typ,
sondern auch statischen Typ zu variieren.
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.
Klassische Generizität
in Eiffel und C++
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
..
feature
push ( elem: INTEGER ) is
do .. end
top: INTEGER is
do .. end
end

class STACK [T]
..
feature
push ( elem: T ) is
do .. end
top: T is
do .. end
end

Anwendung:

Anwendung:
intstack : STACK;
intstack.push(3);
intstack : STACK[INTEGER];
intstack.push(3);
Mehrere Parametertypen

Ausserdem mehrere generische
Parametertypen möglich.
 Beispiel:
Ein generisches Paar in Eiffel
class 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
char
i,j;
i,j;
swap<int> (i,j); //a swap for int
swap<char>(i,j); //a swap for char
Methoden Generizität (II)

Im Gegensatz zu generischen Klassen können
Typparameter bei Methoden implizit aus
Argumenten abgeleitet werden!
Beispiel in C++:
int
float
i,j;
i,j;
swap(i,j); // a swap for int
swap(i,j); // a swap for float
Ohne explizite
Datentypangabe.
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.
Java
Ü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 Klasse
class 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-Operation
static <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 Liste
class SortedList<T
extends Entry
implements Comparable> {..}
Übersetzung (I)
Java
Implementierung von
Generizitäts-Erweiterungen
auf Ebene der Übersetzung.
Übersetzung
(Bytecode)
Ausführung
(JVM)
Linux
Windows
..
Plattformen
Mobile
Übersetzung (II)


Oberster Grundsatz:
Die Java Virtual Machine wird nicht verändert!
Homogener Vorgang (für jeden generischen Typen wird
einmalig neuer Code generiert)
1.
Generische Datentypen werden durch untere Grenze ersetzt
(Erasures).
2.
Casting geschieht bei Attributzugriffen und
Methodenrückgaben.
3.
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();
}
class Cell {
Object value;
Object getValue();
}
..
..
String f(Cell<String> cell){
return cell.value;
}
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

Erlaubt
Stack 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


Jetzt: Kovariante Rückgabetypen


Rückgabetyp einer Methode muss identisch sein mit der
überschriebenen Methode.
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>.
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);
Typfehler!
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.

1.
Ermöglicht bessere Lesbarkeit durch anonyme Typparameter.
interface Collection<E> {
boolean addAll(Collection<? extends E> c);
boolean containsAll(Collection<?> c);
}
2.
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
C#
Ü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
erhalten.
nicht-generisches .NET Framework bleibt
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 Liste
class 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: swap
static 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
..
C#
Java#
Übersetzung
(MIL)
Implementierung von
Generizitäts-Erweiterungen
auf Ebene der Ausführung.
Ausführung
(CLR)
(Linux)
Windows
..
Plattformen
Mobile
Ü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.
Legacy-Code (II)
System.Collections
System.Collections.Generics
Comparer
Comparer<T>
HashTable
Dictionary<K,T>
ArrayList
List<T>
Queue
Queue<T>
SortedList
SortedDictionary<K,T>
Stack
Stack<T>
ICollection
ICollection<T>
System.Comparable
IComparable<T>
IComparer
IComparer<T>
Idictionary
IDictionary<K,T>
Ienumerable
IEnumerable<T>
IEnumerator
IEnumerator<T>
IKeyComparer
IKeyComparer<T>
IList
IList<T>
Typenvarianz
 Nur
Invariante Rückgabetypen
 Rückgabetyp
einer Methode muss identisch
sein mit der überschriebenen Methode.
 Keine
Wildcard-Syntax.
 Keine
Kontravarianz.
Exceptions

Vollständige Unterstützung.
 Generics
dürfen von System.Exception
ableiten.
 Typvariablen sind
erlaubt in throws – Anweisung.
 erlaubt in catch – Anweisung, wenn sie vom Typ
System.Exception ableiten.

Instanziieren von Parametertypen

Parametertypen können instanziiert werden, wenn sie einen
Konstruktor ohne Argumente bieten.

Garantie muss als zusätzliches Constraint „new()“ angegeben werden.
Beispiel:
class Singleton<T> where T : new() {
private T instance;
public T getInstance() {
if (instance == null)
instance = new T();
}
}
return instance;
Reflection

Volle Reflectionunterstützung von generischen Typen.
Type type = typeof(List<int>);

Type-Klasse wird erweitert mit speziellen Eigenschaften von
generischen Typen:
int
bool
bool
bool
bool
virtual Type
virtual Type[]
virtual Type

GenericParameterPosition{virtual get;}
HasGenericParameters{get;}
HasUnboundGenericParameters{virtual get;}
IsGenericParameter{virtual get;}
IsGenericTypeDefinition{virtual get;}
BindGenericParameters(Type[] typeArgs);
GetGenericParameters();
GetGenericTypeDefinition();
Erlaubt auch Zugriff auf aktuelle Typparameter.
Zusammenfassung

Vorteil




Intuitive und vollständige Umsetzung von generischen Typen.
Effizienz.
Vollständige Typkenntnis zur Laufzeit (Reflection).
Nachteil




Absolute Trennung zwischen generischen und nicht
generischem Code.
API kann nicht rückwärtskompatibel umgeschrieben werden.
Keine Kontravarianz.
Umständlichere Notation.
Konklusion
Fazit

Generizität
 Sinnvoll und allgemein anerkannt.
 Birgt aber auch zusätzliche Komplexität.
Nicht immer
klar, wann angebracht.

Implementationen
 Java
 Rückwärts-Kompabilität positiv, allerdings grosse
Einschränkungen bei Nutzung von generischen Typen.
 C#
 Gute Unterstützung für generische Typen, aber unklare
Konsequenz für .NET Framework.
Referenzen

Adding Generics to the Java Programming
Language: Public Draft Specification, Version
2.0
Bracha, Cohen, Kemper, Odersky, Stoutamire, Thorup, Wadler

Variant Generic Types
Sun Microsystems

Design an Implementation of Generics for
the .NET Common Language Runtime
Andrew Kennedy, Don Syme

MSDN Article: Introduction to C# Generics
Juval Lowy
Herunterladen