Generics

Werbung
8. Parametrisierbare Klassen
Grundlagen
8. Parametrisierbare Klassen
Java now supports generics, the most significant change to the language since
the addition of inner classes in Java 1.2 — some would say the most significant
change to the language ever.
M. Naftalin, P. Wadler, Java Generics, O’Reilly, 2006.
Peter Becker, Programiersprache Java — FH Bonn-Rhein-Sieg, SS 08
203
8. Parametrisierbare Klassen
Grundlagen
Beispiel: Stapel (Stack)
Wichtige abstrakte Datentypen bzw. Datenstrukturen der Informatik sind unabhängig
von einem Basis- bzw. Komponententyp.
Beispiel: Es sei T ein beliebiger Datentyp. Ein Stapel (Stack) unterstützt die folgenden Operationen:
• void push(T element)
Legt das Element e vom Typ T auf dem Stapel ab.
• T pop()
Entfernt das oberste Element vom Stapel und liefert es als Ergebnis zurück.
☞ Die Anweisungen zur Implementierung eines Stapels sind unabhängig vom Komponententyp T .
Peter Becker, Programiersprache Java — FH Bonn-Rhein-Sieg, SS 08
204
8. Parametrisierbare Klassen
Grundlagen
Generische Objektreferenz durch Object (1)
Zunächst kein Problem, es gibt ja die Klasse Object.
public class Stapel
{
...
void push(Object element) { ... }
Object pop() { ... }
...
}
☞ Vorgehensweise bis Java 1.4
Peter Becker, Programiersprache Java — FH Bonn-Rhein-Sieg, SS 08
205
8. Parametrisierbare Klassen
Java Generics
Generische Objektreferenz durch Object (2)
Probleme:
• Downcasting notwendig nach pop()
T t = (T) stack.pop();
• Keine Typüberprüfung zur Übersetzungszeit
Stack stack = new Stack();
stack.push( new Integer(4711) ); // wird vom Compiler
stack.push( "hallo!" );
// anstandslos akzeptiert
• Evtl. Typfehler zur Laufzeit
Integer i = (Integer) stack.pop(); // ClassCastException
Peter Becker, Programiersprache Java — FH Bonn-Rhein-Sieg, SS 08
206
8. Parametrisierbare Klassen
Java Generics
Generische Objektreferenz durch Object (3)
Und wenn wir für jeden benötigten Komponententyp eine eigene Implementierung
bereitstellen?
• hoher Aufwand
– Mehrfache Verwendung des fast gleichen Quelltextes
– Mehrfaches Testen notwendig
– entdeckte Fehler müssen mehrfach korrigiert werden
• geringe Abstraktion
Peter Becker, Programiersprache Java — FH Bonn-Rhein-Sieg, SS 08
207
8. Parametrisierbare Klassen
Java Generics
Typvariablen
• Seit Java 5 sind Typvariablen bei einer Klassendefinition möglich.
• Die damit realisierte Klasse entspricht einem generischen Datentyp.
• In der objektorientierten Programmierung bezeichnen wir dies auch als parametrische Polymorphie.
• In Java bezeichnet man diese Möglichkeit als Generics.
Beispiel:
class Stapel<T>
{
...
void push(T element) { ... }
T pop() { ... }
...
}
Peter Becker, Programiersprache Java — FH Bonn-Rhein-Sieg, SS 08
208
8. Parametrisierbare Klassen
Java Generics
Die Typvariable T ersetzt innerhalb der Klassendefinition einen konkreten Typ.
Peter Becker, Programiersprache Java — FH Bonn-Rhein-Sieg, SS 08
209
8. Parametrisierbare Klassen
Java Generics
Nutzung generischer Typen (1)
Für die Instanziierung müssen wir die Typvariable durch einen konkreten Typ ersetzen.
Stapel<String> sstapel = new Stapel<String>();
Stapel<Double> dstapel = new Stapel<Double>();
Die Stapel sind typsicher:
dstapel.push("text");
// hier meckert der Compiler
Kein Downcasting notwendig:
Double d = dstapel.pop();
// kein Problem
Peter Becker, Programiersprache Java — FH Bonn-Rhein-Sieg, SS 08
210
8. Parametrisierbare Klassen
Java Generics
Nutzung generischer Typen (2)
Leider wäre die folgende Deklaration nicht erlaubt:
Stapel<int> istapel = new Stapel<int>();
Grund: Typvariablen dürfen nur durch Referenztypen instanziiert werden.
Wie können wir denn dann einen Integer-Stapel erzeugen? Verwendung von WrapperKlassen: Integer, Double, etc.
Stapel<Integer> istapel = new Stapel<Integer>();
Peter Becker, Programiersprache Java — FH Bonn-Rhein-Sieg, SS 08
211
8. Parametrisierbare Klassen
Java Generics
Exkurs: Wrapper-Klassen (Hüllklassen)
• Instanzen von Wrapper-Klassen (Hüllklassen) haben die Aufgabe, einen primitiven
Wert als Objekt zu repräsentieren.
• Es gibt für jeden einfachen Datentyp eine zugehörige Wrapper-Klasse, z.B.
int/Integer, double/Double, char/Character.
• Integer i = new Integer(4711);
• Wie Strings sind Instanzen von Wrapper-Klassen grundsätzlich unveränderlich (immutable).
Peter Becker, Programiersprache Java — FH Bonn-Rhein-Sieg, SS 08
212
8. Parametrisierbare Klassen
Java Generics
Exkurs: Boxing und Unboxing
• Die Repräsentation eines einfachen Wertes als Objekt mit Hilfe einer WrapperKlasse bezeichnen wir auch als Boxing.
Integer io = new Integer(4711); // Boxing
• Der Zugriff auf den einfachen Wert nennen wir Unboxing.
int ival = io.intValue(); // Unboxing
Peter Becker, Programiersprache Java — FH Bonn-Rhein-Sieg, SS 08
213
8. Parametrisierbare Klassen
Java Generics
Exkurs: Autoboxing (1)
• Die manuelle Ausführung von Boxing und Unboxing ist oft unhandlich.
Stapel<Integer> istapel = new Stapel<Integer>();
istapel.push( new Integer(4711) );
int iv = istapel.pop().intValue();
• Die automatische Umwandlung von Werten einfacher Datentypen in Instanzen einer Wrapper-Klasse und umgekehrt wird als Autoboxing bezeichnet.
• Java beherrscht Autoboxing seit Java 5.
int i
= 4711;
Integer j = i;
int k
= j;
// automatisches Boxing
// automatisches Unboxing
Peter Becker, Programiersprache Java — FH Bonn-Rhein-Sieg, SS 08
214
8. Parametrisierbare Klassen
Java Generics
Exkurs: Autoboxing (2)
Damit ist natürlich auch möglich:
Stapel<Integer> istapel = new Stapel<Integer>();
istapel.push(4711);
// Boxing
int iv = istapel.pop();
// Unboxing
Vorsicht bei Vergleichen mit == und Autoboxing!
Peter Becker, Programiersprache Java — FH Bonn-Rhein-Sieg, SS 08
215
8. Parametrisierbare Klassen
Java Generics
Typanpassungen
Ein instanziierter generischer Typ lässt sich durch Typanpassung auf eine allgemeine
Form bringen:
Stapel<Integer> istapel = new Stapel<Integer>();
Stapel
stapel = (Stapel) istapel;
Jetzt findet für stapel keine Typprüfung mehr statt. Daher würde
stapel.push("No Integer");
keinen Fehler zur Übersetzungszeit liefern.
Peter Becker, Programiersprache Java — FH Bonn-Rhein-Sieg, SS 08
216
8. Parametrisierbare Klassen
Java Generics
Typvariablen in Methoden
Typvariablen können auch auf Methodendeklarationen beschränkt sein:
class Util
{
public static <T> T zufall(T o1, T o2)
{
return Math.random() > 0.5 ? o1 : o2;
}
}
Die Angabe von <T> beim Klassennamen entfällt und verschiebt sich auf die Methodendefinition.
☞ Typinferenz
Peter Becker, Programiersprache Java — FH Bonn-Rhein-Sieg, SS 08
217
8. Parametrisierbare Klassen
Realisierung von Generics
Realisierung von Generics
• heterogen
Für jeden konkreten Typ wird individueller Code erzeugt.
Diesen Ansatz verfolgt z.B. C++ (templates directory)
• homogen
kein individueller Code, Object statt der Typvariablen, Typanpassungen für einen
konkreten Typ, Typüberprüfungen zur Übersetzungszeit
Realisierung für Java
Peter Becker, Programiersprache Java — FH Bonn-Rhein-Sieg, SS 08
218
8. Parametrisierbare Klassen
Realisierung von Generics
Typlöschung
Zur Laufzeit ist der konkrete Typ der Typvariablen nicht bekannt. Dies hat Konsequenzen für instanceof.
Folgendes liefert einen Übersetzungsfehler:
Stapel<String> stapel = new Stapel<String>();
if ( stapel instanceof Stapel<String> ) //
...
Grund: Zur Laufzeit liegt nur die Typinformation Stapel vor.
Stapel<String> stapel1 = new Stapel<String>();
Stapel<Integer> stapel2 = new Stapel<Integer>();
System.out.println(stapel1.getClass() == stapel2.getClass()); // true!
Peter Becker, Programiersprache Java — FH Bonn-Rhein-Sieg, SS 08
219
8. Parametrisierbare Klassen
Realisierung von Generics
Raw-Type
Eine Instanziierung mit einem konkreten Typ muss nicht unbedingt stattfinden. Ohne
Instanziierung gilt implizit Object als konkreter Typ.
Stapel stapel = new Stapel<String>();
Stapel ostapel = new Stapel();
Solche Typen heissen Raw-Type.
Raw-Typen können wir die parametrisierten Typen verwendet werden. Es findet aber
keine Typüberprüfung statt.
Eventuell gibt der Compiler Warnungen aus.
☞ Vorsicht vor Typroblemen zur Laufzeit.
Peter Becker, Programiersprache Java — FH Bonn-Rhein-Sieg, SS 08
220
8. Parametrisierbare Klassen
Typeinschränkungen bei Generics
Typeinschränkungen bei Generics
Die zugelassenen konkreten Datentypen können mit extends auf Untertypen eines
Obertyp eingeschränkt werden.
Beispiel: Ein typsicheres generisches max:
public static <T extends Comparable> T max(T o1, T o2)
{
return o1.compareTo(o2) > 0 ? o1 : o2;
}
☞ extends auch für Untertypen von Schnittstellen!
☞ Das ist besser als
public static Comparable max(Comparable o1, Comparable o2)
Warum?
Peter Becker, Programiersprache Java — FH Bonn-Rhein-Sieg, SS 08
221
8. Parametrisierbare Klassen
Typeinschränkungen bei Generics
Einschränkung mit super
• Mit super statt extends kann eine Einschränkung auf Obertypen statt auf Untertypen erfolgen.
• <T super O>
• Damit dürfen für die Typvariable T O oder Obertypen von O eingesetzt werden.
• Diese Einschränkung wird z.B genutzt, wenn eine mit T parametrisierte generische
Datenstruktur manipuliert wird (Kontravarianz).
Peter Becker, Programiersprache Java — FH Bonn-Rhein-Sieg, SS 08
222
8. Parametrisierbare Klassen
Typeinschränkungen bei Generics
Kombinationen von Obertypen
• Soll die Typvariable T zu mehreren Obertypen passen, lassen sich Kombinationen
bilden.
• Man beachte: Für T kann nur eine Oberklasse angegeben werden, da es in Java
nur einfache Vererbung bei Klassen gibt.
• Weitere Obertypen müssen Schnittstellen sein.
• <T extends O & I1 & I2 & ...>
Peter Becker, Programiersprache Java — FH Bonn-Rhein-Sieg, SS 08
223
8. Parametrisierbare Klassen
Typeinschränkungen bei Generics
Generics und Vererbung (1)
• Wenn Fussballer eine Unterklasse von Sportler ist, dann ist Fussballer[]
ein Untertyp von Sportler[].
• Daher ist folgenden problemlos möglich:
Fussballer[] fussballer = new Fussballer[11];
Sportler[] sportler = fussballer;
• In der objektorientierten Programmierung bezeichnet man dies als Kovarianz.
• Beispiel:
public static void gebeSportlerAus(Sportler[] sportler) {
for (int i=0 ; i<sportler.length ; i++)
sportler[i].gibAus();
}
Kovarianz stellt bei lesendem Zugriff kein Problem dar.
Peter Becker, Programiersprache Java — FH Bonn-Rhein-Sieg, SS 08
224
8. Parametrisierbare Klassen
Typeinschränkungen bei Generics
Generics und Vererbung (2)
• Generics sind untereinander nicht kovariant sondern invariant!
• D.h. Stapel<Fussballer> ist kein Untertyp von Stapel<Sportler>.
• Daher liefert der Compiler für folgende Anweisungen einen Fehler:
Stapel<Sportler> sportler = new Stapel<Fussballer>();
• Begründung: Wenn erlaubt, dann wäre folgendes möglich:
sportler.push(new Handballer());
Peter Becker, Programiersprache Java — FH Bonn-Rhein-Sieg, SS 08
225
8. Parametrisierbare Klassen
Typeinschränkungen bei Generics
Generics und Vererbung (3)
• Kovarianz stellt bei schreibendem Zugriff ein Problem dar!
• Dagegen wäre folgendes vom Prinzip her unproblematisch:
Stapel<Sportler> sportler = new Stapel<Mensch>();
sportler.push(new Handballer());
sportler.push(new Sportler());
• Kontravarianz
Peter Becker, Programiersprache Java — FH Bonn-Rhein-Sieg, SS 08
226
8. Parametrisierbare Klassen
Typeinschränkungen bei Generics
Generische Variablen: Wildcards
Folgendes funktioniert nicht:
Stapel<Sportler> sportler;
Stapel<Fussballer> fussballer = new Stapel<Fussballer>();
Stapel<Handballer> handballer = new Stapel<Handballer>();
sportler = fussballer;
// Fehler!
• Es ist aber möglich, statt konkreter Typangaben Wildcards anzugeben.
• Diese können mit zusätzlichen Einschränkungen (extends, super) versehen
werden.
Peter Becker, Programiersprache Java — FH Bonn-Rhein-Sieg, SS 08
227
8. Parametrisierbare Klassen
Typeinschränkungen bei Generics
Generische Variablen: Wildcards (2)
Beispiel:
Stapel<? extends Sportler> sportler;
Stapel<Fussballer> fussballer = new Stapel<Fussballer>();
Stapel<Handballer> handballer = new Stapel<Handballer>();
Stapel<Biertrinker> biertrinker = new Stapel<Biertrinker>();
sportler = fussballer; sportler = handballer;
// OK!
sportler = biertrinker;
// Fehler!
Peter Becker, Programiersprache Java — FH Bonn-Rhein-Sieg, SS 08
228
8. Parametrisierbare Klassen
Typeinschränkungen bei Generics
Generische Variablen: Wildcards (3)
Wildcard mit extends liefert uns Kovarianz: Für
public static void gebeSportlerAus(Stapel<? extends Sportler> sportler) {
for (Sportler s : sportler) s.gibAus();
}
ist
Stapel<Fussballer> fussballer = new Stapel<Fussballer>();
gebeSportlerAus(fussballer);
kein Problem.
Peter Becker, Programiersprache Java — FH Bonn-Rhein-Sieg, SS 08
229
Herunterladen