Generisches Programmieren Generisches Programmieren 24.11.11 Uli Dormann 1 Inhaltsverzeichnis Motivation Ansatz Sprachen mit generischer Programmierung Generische Klassen Namenskonvention Prüfung Typsicherheit Generische Klassen, Interfaces Schachteln Der Diamantoperator Typebounds Wildcard Vererbung Übersetzen generischer Klassen Type Erasure Generics und Arrays Brückenmethoden Grenzen generischer Typen Polymorphie Abschluss 2 Motivation (1) Beispiel Tasche Für jede Möglichkeit eine eigene Klasse String Integer public class Pocket public class Pocket { { private String value; private int value; public Pocket() {} public Pocket() {} public Pocket( String value ) { this.value = value; } public Pocket( int value ) { this.value = value; } public void set( String value ) { this.value = value; } public void set( int value ) { this.value = value; } public String get() { return value; } public int get() { return value; } public boolean isEmpty() { return value == null; } public boolean isEmpty() { return value == null; } public void empty() { value = null; } public void empty() { value = null; } 3 Motivation (2) Beispiel: Tasche Objekt public class Pocket { private Object value; public Pocket() {} public Pocket( Object value ) { this.value = value; } public void set( Object value ) { this.value = value; } public Object get() { return value; } public boolean isEmpty() { return value == null; } public void empty() { value = null; } } Unpraktisch und nicht Typsicher! 4 Ansatz Generischer Algorithmus Mechanismus zur Verwendung solcher Algorithmen In Java „Generics“ mit Version 5 Generics sind Objekte, keine primitiven Datentypen. Primitive Typen: boolean, char, byte, short, int, float, double AutoBoxing : Primitiver Datentyp ↔ Wrapper Objekt 5 Sprachen mit generischer Programmierung Java NET-Sprachen ADA Delphi HaXe ABAP Eiffel ML Python C# C++ Makros in C (wenn auch nicht dafür vorgesehen) 6 Generische Klassen (1) Typparameter wird durch Typvariable ersetzt public class Pocket<T> { private T value; public Pocket() {} public Pocket( T value ) { this.value = value; } public void set( T value ) { this.value = value; } public T get() { return value; } public boolean isEmpty() { return value != null; } public void empty() { value = null; } } 7 Generische Klassen (2) Typvariablen (fast) wie ein normaler Typ Mehrere Typvariablen möglich Beispiel: public class Pocket<T> Pocket<Integer> intPocket = new Pocket<Integer>(); Pocket<String> stringPocket = new Pocket<String>(); Begriff Beispiel Generische Typen Pocket<T> Typvariable T Parametrisierter Typ Pocket<Long> Typparameter Long Originaltyp Pocket 8 Namenskonvention Einzelne Großbuchstaben z.B. E wie Element Keine Wörter Der Quellcode wird öfter gelesen als geschrieben, deswegen leserlich halten. 9 Prüfung Typsicherheit Zwei Instanzen, die die Typen prüfen - JVM - Compiler → Compiler braucht mehr Informationen 10 Generische Klassen, Interfaces → class Pocket <T> → interface XXX<T> 11 Schachteln Typvariable T beschränkt sich nicht auf Klassen- oder Schnittstellentypen, sondern kann auch wieder ein generischer Typ sein. Pocket<Pocket<String>> pocketOfPockets = new Pocket<Pocket<String>>(); pocketOfPockets.set( new Pocket<String>() ); pocketOfPockets.get().set( "Inner Pocket<String>" ); System.out.println( pocketOfPockets.get().get() ); // Inner Pocket<String> Ohne Generics sähen alle Taschen gleich aus. 12 Der Diamantoperator Ab Java 7 wird aus: Pocket<Pocket<String>> pocketOfPockets = new Pocket<Pocket<String>>(); einfach: Pocket<Pocket<String>> pocketOfPockets = new Pocket<>(); 13 Typebounds bedeutet: Kompatibel zu Klassen und Interfaces möglich Kann mehrere geben class Pocket<T extends A & B>{... Legt Mindestanforderung fest Kann die Typvariable enthalten class Pocket<T extends Comparable<T>> {... 14 Wildcard Bewusstes vergessen der Typinformation (Joker ;) Pocket<Number> b; Pocket<Integer> bI = new Pocket <Integer>(); Pocket<Double> bD = new Pocket <Double>(); b = bI; // Nein Pocket<? extends Number> b; Pocket<Integer> bI = new Pocket<Integer>(); Pocket<Double> bD = new Pocket <Double>(); Pocket<String> bS = new Pocket <String>(); b = bI; // Ja b = bS; // Nein 15 Vererbung (1) Invarianz Verschiedene generische Typen sind zueinander inkompatibel, unabhängig von der Kompatibilität ihrer Typargumente Covarianz Zu Wildcard-Typen mit Upper-Typebound (C<? extends B>) sind alle generischen Typen kompatibel, deren Typargument zu B kompatibel ist 16 Vererbung (2) Varianzenübersicht Typ Kompatible Typargumente Invarianz C<T> T Bivarianz C<?> Alle Covarianz C<? extends B> B und abgeleritete Typen Contravarianz C<? super B> B und Basistypen 17 Übersetzen generischer Klassen Ansatz: Generische Datentypen werden in Java ausschließlich vom Compiler verarbeitet Laufzeitsystem weiß nichts von generischen Typen Übersetzung: Generischer Code wird in nicht generischen Code umgewandelt Aus jeder generischen Klasse wird eine nicht-generische Klasse generiert und in eine .class-Datei übersetzt → In C++ wird dagegen jede Instanziierung einer Klasse mit Typargumenten getrennt übersetzt -> dadurch langsameres Übersetzen und größere Kompilate aber bessere Optimierungsmöglichkeiten und weniger Einschränkungen 18 Type Erasure (1) Typ-Variablen in spitzen Klammern löschen Vorkommen von Typvariablen mit einem oder mehreren Typebounds durch den einzigen bzw. ersten Typebound ersetzen Vorkommen von Typvariablen ohne Typebounds durch Object ersetzen Die Typ-Korrektheit statisch prüfen (d.h. zum Übersetzungszeitpunkt) Typargumente müssen allen Typebounds genügen Generische Typen müssen auch untereinander korrekt verwendet werden, insbesondere bei Wildcard-Typen Typargumente, einschließlich Wildcards, in spitzen Klammern gelöscht Typecasts eingeschoben, wo der Wert eines Typarguments benutzt wird 19 Type erasure (2) Beispiel: Ohne Typebounds Generische Klasse public class Pocket<T> { private T value; public Pocket() {} public Pocket( T value ) { this.value = value; } Nach Type-Erasure public class Pocket { private Object value; public Pocket() {} public Pocket( Object value ) { this.value = value; } public void set( T value ) { this.value = value; } public void set( Object value ) { this.value = value; } public T get() { return value; } public boolean isEmpty() { return value != null; } public Object get() { return value; } public boolean isEmpty() { return value != null; } public void empty() { value = null; } } public void empty() { value = null; } } 20 Generics und Arrays (1) Nicht möglich: class TwoBox<T> { private T[ ] array = new T[ 2 ]; T[ ] getArray() { return array; } } // Fehler Cannot create a generic array of T Grund: class TwoBox { Object[ ] array = new Object[ 2 ]; Object[ ] getArray() { return array; } } // (1) 21 Generics und Arrays (2) Parametriert: TwoBox<String> twoStrings = new TwoBox<String>(); String[ ] stringArray = twoStrings.getArray(); Compiler generiert: TwoBox twoStrings = new TwoBox(); String[ ] stringArray = (String[ ]) twoStrings.getArray(); // (2) Lösung: Reflection T[ ] array = (T[]) Array.newInstance( clazz, 2 ); 22 Brückenmethoden Schnittstelle Comparable: public interface Comparable<T> { public int compareTo( T o ); } Klasse Integer: public final class Integer extends Number implements Comparable<Integer> Neu angelegte Brückenmethode: public int compareTo( Object anotherInteger ) { return compareTo( (Integer) anotherInteger ); } 23 Grenzen generischer Typen (1) Primitive Typargumente Node<int> ni = new Node<int>(23); // Fehler! Aber: Node<Integer> ni = new Node<Integer>(23); // Autoboxing Statische Elemente class Broken<T> { static T data; } // Fehler! Grund: Alle Klassen Broken<T> teilen sich das Klassenattribut data. Welchen Typ soll es haben? Dynamische Typprüfung class Pocket<T> { boolean isCompatible(Object o) { return o instanceof T; } } // Type-Erasure: o instanceof T → o instanceof Object Fehler! 24 Grenzen generischer Typen (2) Typecasts Class Node<T> { T info; void setInfo(Object o) { info = (T) o; } } // Sinnlos Type-Erasure: (T) o → (Object) o Compiler erzeugt: „warning: unchecked cast of type T“ Konstruktoraufrufe class Node<T> { T info; Node() { info = new T(); } } // Fehler Woher soll Java wissen, dass T einen Standard-Konstruktor hat? Beispiel: Node<Integer> ni = new Node<Integer>(); Ausweg: class Node<T> { T info; Node(T i) { info = i; } } Node<Integer> ni = new Node<Integer>(23); Generische Basistypen import java.util.Date; class Timestamped<T> extends T { Date timestamp = new Date(); } // Fehler Generische Klasse muss Konstruktor der Basisklasse aufrufen können. Dieser kann hier aber nicht zur Kompilierzeit ermittelt werden! 25 Grenzen generischer Typen (3) Exceptions class UniversalException<T> extends Exception { T reason; } // Fehler Generische Typen können nicht für Exceptions verwendet werden, da das Fangen mit catch auf dem Ermitteln des Typs des geworfenen Objekts basiert. Dieser geht aber bei der Type-Erasure verloren. Compiler erzeugt: „a generic class may not extend java.lang.Throwable“ 26 Polymorphie und Generics Definition Polymorphe Methoden sind unabhängig von generischen Typen Sie können auch in nicht-generischen Klassen definiert werden 27 Abschluss Fragen? Danke für die Aufmerksamkeit! 28 Quellen Java ist auch eine Insel Uni Bremen Unterlagen zu PI-1 Wikipedia Www.dpunkt.de Uni Hamburg Unterlagen SE-1 29