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