Generics Generische Interfaces, Klassen und Methoden Grundlagen relativ einfach – technische Details teilweise komplex Erweiterung der Typisierung in Java durch generische Datentypen : Auch für Typangaben sind nun Platzhalter möglich Konzept der übergebbaren Typ-Parameter / Typ-Variablen Vermeidung der Typ-"Wollmilchsau" namens Object Intensive Nutzung u.a. innerhalb Collection Framework … Alle Details : F.Esser, "Das Tiger Release – Java 5 im Einsatz", Galileo, 2005, Kap 1 + 2 M.Naftalin, P.Wadler, "Java Generics and Collections", O'Reilly, 2007 A.Langer, www.angelikalanger.com/GenericsFAQ/JavaGenericsFAQ.html © K.G. Deck , H.Neuendorf Programmieren 2 - H.Neuendorf (2) Generics Situation vor der Einführung von Generics Grundlegende Generics-Sprachelemente Anmerkung : Reflection ist eine Laufzeit-Technologie Generics ist eine Compilezeit-Technologie ! Somit werden komplementäre Leistungen erbracht – aber bei Generics schon zur Compilezeit. © K.G. Deck , H.Neuendorf Programmieren 2 - H.Neuendorf (3) Generics – Situation vor Java 5 Typ-Unsicherheit bei pauschaler Verwendung des total-generischen (raw) Typs Object : Unklare Semantik + Riskante Downcasts class Paar { // vor Java 5 : class PaarTest { private Object eins ; public static void main( String[] args ) { private Object zwei ; Mitarbeiter m = new Mitarbeiter( "Meier" ) ; public Paar( Object e, Object z ) { Konto k = new Konto( 4711 ) ; eins = e ; Paar p = new Paar( m, k ) ; // erlaubt zwei = z ; } // ..... public Object getEins( ) { return eins ; } Mitarbeiter m1 = (Mitarbeiter) p.getEins( ) ; public Object getZwei( ) { return zwei ; } Mitarbeiter m2 = (Mitarbeiter) p.getZwei( ) ; } } } Problem : Wie lässt man beliebige Referenztypen zu - und hat trotzdem Typsicherheit bei konkreter Verwendung ? … ClassCastException hier steht ein Konto, kein Mitarbeiter z.B. Paarklasse für beliebige Typen – aber paarweise typgleich © K.G. Deck , H.Neuendorf Programmieren 2 - H.Neuendorf (4) Generics – Situation vor Java 5 Nicht generische Lösung : Für jeden Typ eine eigene Klasse class Paar { private Object eins ; private Object zwei ; public Paar( Object e, Object z ) { eins = e ; } zwei = z ; class PaarString { private String eins ; private String zwei ; public Paar( String e, Stringclass z ) {PaarInteger { private Integer eins ; eins = e ; zwei = z ; public Object getEins( ) { return eins ; }} } private Integer zwei ; public Object getZwei( ) { return zwei ;public } public Integer e, Integer z ) { String getEins( ) { return eins Paar( ;} public String getZwei( ) { returneins zwei= ;e}; zwei = z ; } } public Integer getEins( ) { return eins ; } public Integer getZwei( ) { return zwei ; } } Problem : … ohne dass man viele unterschiedliche Klassen (für jede Typkonstellation eine) implementiert - die sich kaum unterscheiden und somit identisches Coding duplizieren ? © K.G. Deck , H.Neuendorf Programmieren 2 - H.Neuendorf (5) Generics – Generische Typen ab Java 5 Compile-Zeit-Technik ! Datentyp, dessen Komponenten durch Typ-Parameter / Typ-Variablen spezifiziert sind Bei identischem Code kann der Datentyp in verschiedenen Typausprägungen eingesetzt werden Java-Klassen lassen sich hinsichtlich der von ihnen verwendeten (globalen) Typen parametrisieren Generics geben statische Typsicherheit beim Kompilieren – keine LZ-Typfehler ! Typ-Parameter <> als variable Platzhalter für beliebige Referenztypen Verwendbar in Interfaces, Klassen und Methoden : interface Lookup<T> { class GenPaar<T> { // ... T getInstance( String name ) ; } } class Test { public static <T> boolean check( T param ) { // ... } } Eine Typ-Variable repräsentiert einen zum Zeitpunkt der Implementierung noch unbekannten Referenztyp. Erst bei Verwendung wird ein aktueller konkreter Typ eingesetzt. <T> bedeutet im Grunde <T extends Object> Somit Methoden von Object auf Variablen vom Typ T zur Verfügung : .toString( ); .hashcode( ); etc. funktioniert Nur für Referenztypen ! Nicht für primitive Typen ! © K.G. Deck , H.Neuendorf Programmieren 2 - H.Neuendorf (6) Generics – Generische Klassentypen class GenPaar<T> { // Generics : private T eins ; private T zwei ; public GenPaar( T e, T z ) { eins = e ; zwei = z ; } public T getEins( ) { return eins ; } public T getZwei( ) { return zwei ; } Es gibt stets nur eine Klasse GenPaar – jedoch in beliebig vielen verschiedenen Typ-Parametrisierungen durch verschiedene Typ-Zuweisungen ! Identisches Class-Objekt Nur ein .class-File } Direkt hinter Klassennamen stehen in spitzen Klammern <> ein oder mehrere Typplatzhalter (Typliste) Stehen unter ihrem Platzhalter-Namen im Code zur Verfügung Generische Typ-Deklaration macht GenPaar zur generischen Klasse Beim Erzeugen von GenPaar-Objekten muss ein konkretes Typ-Argument eingesetzt werden (generic type invocation) – dadurch entsteht ein aktuell parametrisierter Typ. © K.G. Deck , H.Neuendorf Programmieren 2 - H.Neuendorf (7) Generics – Generische Klassentypen class GenPaar<T> { // Generics : private T eins ; private T zwei ; public GenPaar( T e, T z ) { class Test { eins = e ; public static void main( String[] args ) { zwei = z ; } public T getEins( ) { return eins ; } public T getZwei( ) { return zwei ; } Mitarbeiter ma = new Mitarbeiter( "Meier" ) ; } Mitarbeiter mb = new Mitarbeiter( "Klaus" ) ; GenPaar<Mitarbeiter> p1 = new GenPaar<Mitarbeiter>( ma, mb ) ; Mitarbeiter m1 = p1.getEins( ) ; // kein Cast nötig ! Mitarbeiter m2 = p1.getZwei( ) ; Konto ka = new Konto( 4711 ) ; Konto kb = new Konto( 1234 ) ; GenPaar<Konto> p2 = new GenPaar<Konto>( ka, kb ) ; } Alle Objekte vom passenden Typ sind zulässig – also auch Unterklassentypen ! zB Girokonto, Sparkonto .. } Versuch, nicht-kompatible Typen zu übergeben liefert Compiler-Fehler ! Also z.B. auch Mischung von Typen: … = new GenPaar<Konto>( ka,mb ) ; © K.G. Deck , H.Neuendorf Programmieren 2 - H.Neuendorf (8) Generics – Generische Klassentypen – Verwendung Raw-Type Generische Klassen lassen sich auch ohne Typ-Eingrenzung verwenden. In diesem Fall wird nicht mit dem parametrisierten Typ sondern mit dem sogenannten Raw-Type gearbeitet Es wird dadurch intern doch wieder Object als Typ verwendet ! Der Raw-Type ist zugelassen, damit alter Non-Generics-Java-Code (Legacy Code) weiterhin funktioniert. Compiler-Warung : GenPaar is a raw type. References to generic type GenPaar<T> should be parameterized. class Test { public static void main( String[ ] args ) { Mitarbeiter ma = new Mitarbeiter( "Meier" ) ; Mitarbeiter mb = new Mitarbeiter( "Klaus" ) ; GenPaar p1 = new GenPaar( ma, mb ) ; // Warnung ! // Rückgabetyp ist nun wieder Object ! Mitarbeiter m1 = (Mitarbeiter) p1.getEins( ) ; // DownCast wieder nötig ! Mitarbeiter m2 = (Mitarbeiter) p1.getZwei( ) ; } } © K.G. Deck , H.Neuendorf Programmieren 2 - H.Neuendorf (9) Generics – Generische Klassentypen Eine bestimmte Typ-Variable repräsentiet einen (noch unbekannten Referenz-Typ) – sie kann nicht gleichzeitig zwei oder mehr verschiedene Typen repräsentieren ! Eine bestimmte Typ-Variable kann nicht mehrfach deklariert werden ! class GenPaar<T,T> { private T eins ; private T zwei ; Compiler : Duplicate type parameter T public GenPaar( T e, T z ) { eins = e ; zwei = z ; } … es gibt aber auch deutlich subtilere Fehler … // ... } // Fehler : Keine Generics für primitive Typen GenPaar<int, char> prim … und wenn doch verschiedene Typen zugeleassen werden sollen ? … = new GenPaar<int, char>( 5, 'c' ) ; © K.G. Deck , H.Neuendorf Programmieren 2 - H.Neuendorf (10) Generics – Generische Klassentypen Es sind beliebig viele verschiedene Typ-Parameter möglich ! … und wenn verschiedene Typen zugelassen werden sollen ? class GenPaar<T1,T2> { private T1 eins ; Namensgebung beliebig. private T2 zwei ; Jedoch kurze Namen / einzelne Großbuchstaben üblich, um Verwechselung mit Klassennamen zu vermeiden … public GenPaar( T1 e, T2 z ) { eins = e ; zwei = z ; } Compiler checkt Konsistenz public T1 getEins( ) { return eins ; } Compiler-Fehler Type Mismatch : public T2 getZwei( ) { return zwei ; } public T2 getEins( ) { return eins ; } public T1 getZwei( ) { return zwei ; } } class Test { public static void main( String[] args ) { Mitarbeiter ma = new Mitarbeiter( "Meier" ) ; Konto ka = new Konto( 4711 ) ; GenPaar<Mitarbeiter, Konto> p1 = new GenPaar<Mitarbeiter, Konto>( ma, ka ) ; Mitarbeiter m1 = p.getEins( ) ; Konto k2 = p.getZwei( ) ; } } © K.G. Deck , H.Neuendorf Programmieren 2 - H.Neuendorf (11) Generics – Notations-Erleichterungen Java 7 Typ-Parameter muss bei Zuweisungen nicht mehr beidseitig genannt werden Auf rechter Seite kann mit leeren <> (Diamond-Operator) gearbeitet werden, wenn Typausprägung widerspruchsfrei aus dem Kontext (verwendete Objekte) erkennbar ist class GenPaarTest { public static void main( String[] args ) { // Erzeugen der Mitarbeiter- und Konto-Objekte ma, mb, ka, kb // ... GenPaar<Mitarbeiter> p1 = new GenPaar<>( ma, mb ) ; GenPaar<Konto> p2 = new GenPaar<>( ka, kb ) ; GenPaar<String> p3 = new GenPaar<>( "links", "rechts" ) ; GenPaar<Integer> p4 = new GenPaar<>( "ich", "du" ) ; // Fehler – incompatible types ! } } © K.G. Deck , H.Neuendorf Programmieren 2 - H.Neuendorf (12) Generics Technische Eigenheiten – Was geht nicht ? Type bounds Invarianz-Forderung © K.G. Deck , H.Neuendorf Programmieren 2 - H.Neuendorf (16) Generics – Was geht nicht ?? Details sind in Wirklichkeit noch deutlich(!) komplexer … Grundsatz : Von einer generischen Klasse existiert nur eine Bytecode-Version (anders als in C++) ! Es gibt nicht mehrere Varianten für verschiedene Typbelegungen ! Technisch : Type Erasure ! Typisierungsangaben dienen nur dem Compiler zur Durchführung von Typprüfungen. Im Compilat selbst hat der Compiler alle generischen Typ-Informationen getilgt Zur LZ wird mit Raw Type gearbeitet Bsp: Im Speicher befindet sich zur LZ immer nur eine Klasse … … und es gibt auch nur ein .class-File – nicht mehrere ! Man kann keine Dinge tun, die dazu im Widerspruch ständen Auch nur ein Class -Objekt : Stack<String>.class == Stack<Double>.class == Stack.class © K.G. Deck , H.Neuendorf !! Programmieren 2 - H.Neuendorf (17) Generics – Was geht nicht ?? Ein Typ-Parameter T einer generischen Klasse kann nicht als Typ eines statischen Attributs dienen … … und auch nicht in statischen Methoden verwendet werden ! Andernfalls bräuchte man für jede Typ-Parametrisierung eine separate Klasse, die ein eigenes statisches Attribut für die verschiedenen Typisierungen enthält ! Typ-Parameter einer generischen Klasse sind nicht statisch verwendbar ! class Foo<T> { class Bar { public static void main( String[ ] args ) { private static T test ; // Fehler ! public static T get( ) { return test ; } // Fehler ! Foo<String>.set( "Hallo" ) ; public static void set( T t ) { test = t ; } // Fehler ! Foo<Double>.set( new Double(2.5) ) ; // ... // ... } } } © K.G. Deck , H.Neuendorf Programmieren 2 - H.Neuendorf (18) Generics – Was geht nicht ?? // TestErasure.java : public class TestErasure<T extends String> { public T t; public TestErasure( T t ) { this.t = t ; } Type-Erasure ist im Bytecode sichtbar : Mittels javap -c TestErasure Bytecode darstellen Generische Informationen nur im Quellcode Im Bytecode keine generischen Informationen } // TestErasure.class : public class TestErasure extends java.lang.Object { public java.lang.String t; public TestErasure(java.lang.String); Bytecode enthält TypSpezialisierung des generischen Parameters … Code: 0: aLoad-0 Aus T extends String 1: invokespecial #1; //Method java/lang/Object.''<init>'':()V 4: aload-0 wird somit der konkrete Typ String im Bytecode 5: aload-1 6: putfietd 9: return #2; //Field t:Ljava/lang/String; … oder den Typ Object } © K.G. Deck , H.Neuendorf Programmieren 2 - H.Neuendorf (19) Generics – Was geht nicht ?? Compiler führt Type-Erasure durch Konsequenz : Typ-Informationen stehen zur LZ nicht mehr zur Verfügung ! Man kann keine Objekte aus Typ-Parametern erzeugen / class Test<T> { Type-Parameter sind nicht instanziierbar : } T n = new T( ) ; // Fehler – geht nicht ! /* … */ Typ + Konstruktor unbekannt return new T( ) ; Kein new T( ) Man kann keine Arrays aus Typ-Parameter erzeugen : T[ ] arr = new T[10] ; Kein new T[ ] // Fehler – geht nicht ! Workaraound - intern zu T[ ] gecastet ← Class-Objekt enthält nötige Typ-Informationen : public T[ ] test( Class<T> cls , int size ) { // Compiler-Warnung Type safety: Unchecked cast from Object[] to T[] T[ ] arr = ( T[ ] ) java.lang.reflect.Array.newInstance( cls, size ) ; return arr ; } © K.G. Deck , H.Neuendorf Programmieren 2 - H.Neuendorf (20) Generics – Was geht nicht ?? Man kann keine Arrays von parametrisierten Klassen anlegen Test<String> t1 = new Test<String>( ) ; // ok !! Test<String>[ ] t2 = new Test<String>[5] ; // Fehler !! class Test<T> { /* … */ } Test[ ] t2 = new Test[5] ; // ok !! Prüfungen mit instanceof auf parametrisierten Typ nicht erlaubt instanceof erlaubt nur raw Types Kein instanceof T Test<String> t1 = new Test<String>( ) ; boolean b1 = t1 instanceof Test<String> ; // Fehler !! boolean b2 = t1 instanceof Test ; // ok !! Kein Class-Objekt auf Typ-Variable abrufbar : Class c = T.class ; // Fehler !! Kein Class-Objekt für parametrisierte Typen : Class c = Test.class ; // ok !! Class c2 = Test<String>.class ; © K.G. Deck , H.Neuendorf // Fehler !! Programmieren 2 - H.Neuendorf (21) Generics – Einschränkung der Typ-Parameter – type-bound Ziel : Nur bestimmte Klassentypen sollen als Typ-Werte zugelassen werden Mittel : Forderung von Erbschafts- / Implementierungsbeziehungen (Bounds) : < T extends … > class GenPaar< T extends Konto > { private T eins ; private T zwei ; public GenPaar( T e, T z ) { eins = e ; zwei = z ; Nur noch der Typ Konto und seine Untertypen (Unterklassen) können bei Instanziierung + Typisierung von GenPaar für den TypParameter T eingesetzt werden. Andere Klassentypen vom Compiler abgewiesen ! Kein <T super …> bei Klassen und IF } public T getEins( ) { return eins ; } public T getZwei( ) { return zwei ; } } Somit nun nicht nur Methoden von Object sondern auch von Konto auf Variablen vom Typ T zur Vfg : eins.einzahlen( 10.2) ; © K.G. Deck , H.Neuendorf etc. funktioniert Programmieren 2 - H.Neuendorf (22) Generics – Einschränkung der Typ-Parameter – type-bound class GenPaar< T extends Konto > { /* … */ class Test { } public static void main( String[] args ) { Girokonto g1 = new Girokonto( 4711 ) ; Girokonto g2 = new Girokonto( 5678 ) ; GenPaar<Girokonto> p1 = new GenPaar<Girokonto>( g1, g2 ) ; // GenPaar<String> p2 = new GenPaar<String>( "hallo", "alle" ) ; Compilerfehler ! } Bound mismatch: The type String is not a valid substitute for the bounded parameter <T extends Konto> } Aufgenommen werden können in p1 nur Objekte vom Typ Girokonto – und dessen Unterklassen. Also auch keine Konto-Objekte © K.G. Deck , H.Neuendorf Programmieren 2 - H.Neuendorf (23) Generics – Einschränkung der Typ-Parameter – type-bound Man kann mit "&" mehrere Beziehungen fordern : Neben einem Oberklassentyp zusätzlich das Implementieren beliebig vieler Interfaces Nur extends erlaubt - Schlüsselworte implements, super sind nicht zulässig class GenPaar< T extends Konto & Closeable & Comparable & Iterable > { /* ... */ } Auch komplexe Bounds sind erlaubt – oft auch sehr sinnvoll und erforderlich : Beispiel : Sortierter Datenbehälter - fordert Comparable Objekte class SortedSet<T extends Comparable<T> > { /* ... */ } In Worten : In meinem Coding arbeite ich nur mit Typen T, die das Interface Comparble für sich implementieren, z.B.: class Mitarbeiter implements Comparable<Mitarbeiter> { /* ... */ } © K.G. Deck , H.Neuendorf Programmieren 2 - H.Neuendorf (24) Generics – Invarianz-Forderung bei Zuweisungen Auch wenn B Untertyp von A ist, so ist dennoch nicht <B> Untertyp von <A> ! class Bank<T > { public T kto ; public Bank( T t ) { kto = t ; } // ... } Zwar ist Girokonto kompatibel zu Konto aber Bank<Girokonto> ist nicht kompatibel zu Bank<Konto> class BankTest { public static void main( String[] args ) { Girokonto giro = new Girokonto( 4711 ) ; Bank<Girokonto> b1 = new Bank<Girokonto>( giro ) ; // ok ! Type mismatch: cannot convert from Bank<Girokonto> to Bank<Konto> Bank<Konto> b2 = new Bank<Girokonto>( giro ) ; // Fehler ! } } Typ-Parameter müssen invariant sein, damit Zuweisung akzeptiert wird ! // Klassen selbst lassen natürlich Upcast zu – Bsp aus Collection-Framework : Collection<Konto> col = new HashSet<Konto>( ) ; © K.G. Deck , H.Neuendorf // ok – Collection ist Supertype von HashSet Programmieren 2 - H.Neuendorf (26) Generics – Invarianz-Forderung bei Zuweisungen 1. Motivation : Vermeidung von StoreExceptions zur LZ - wie bei Arrays von Referenztypen ! Bank<Konto> b2 = new Bank<Girokonto>( giro ) ; // Compile- Fehler - gut so, denn sonst : b2.kto = new Girokonto( 4711 ) ; // wäre noch ok b2.kto = new Konto( 1234 ) ; // StoreException - Konto ist kein Girokonto Verhindert Infiltration nicht kompatibler Typen : b2 referenziert eine Bank of Girokonto - nicht eine Bank of Konto ... ... und nicht jedes Konto ist ein Girokonto Konto Bank<Konto > zwar ist Girokonto Subtyp zu Konto ... Bank<Girokonto > © K.G. Deck , H.Neuendorf ... aber Bank<Girokonto> ist nicht Subtyp zu Bank<Konto> Girokonto Programmieren 2 - H.Neuendorf (27) Generics – Invarianz-Forderung bei Zuweisungen 2. Motivation : Kein Unterlaufen der Einfach-Klassen-Vererbung in Java durch Generics ! Konto Bank<T> Bank<Konto> Bankfiliale<T> ok !! Girokonto nein !! ? Bankfiliale<Konto> Bank<GiroKonto> ok !! nein !! Bankfiliale<GiroKonto> zwar ist Bankfiliale Subtyp zu Bank und Girokonto Subtyp zu Konto ... ... aber Bank<Girokonto> ist nicht Subtyp zu Bank<Konto> und Bankfiliale<Girokonto> ist nicht Subtyp zu Bankfiliale<Konto> © K.G. Deck , H.Neuendorf Programmieren 2 - H.Neuendorf (28) Generics Vererbung bei generischen Klassen (+ Interfaces) Übernahme von Typparametern Einschränkung von Typparametern Mischverhältnisse © K.G. Deck , H.Neuendorf Programmieren 2 - H.Neuendorf (29) Generics – Vererbung bei Klassen - Bounds Unterklasse kann Typparameter der Oberklasse ignorieren Arbeitet nicht-generisch Unterklasse hat dann keinen eigenen Typparameter – arbeitet mit dem Raw-Type : class Ober<T> { T t ; } Unter u = new Unter( ) ; class Unter extends Ober { } // Raw-Type // u.t ist vom Typ Object Unterklasse kann Typparameter der Oberklasse bei Klassendeklaration festlegen Alle Instanzen der Unterklassen arbeitet mit diesem Typ Unterklasse verwendet Typparameter der Oberklasse + legt diesen fest : class Ober<T> { T t ; } Unter u = new Unter( ) ; class Unter extends Ober<Konto> { } u.t = new Konto( ) ; // u.t ist vom Typ Konto © K.G. Deck , H.Neuendorf Programmieren 2 - H.Neuendorf (30) Generics – Vererbung bei Klassen - Bounds Unterklasse übernimmt Oberklassen-Typparameter - indem sie eigenen, identischen Typparameter angibt Individuelle Festlegung des Typs bei jeder Objekterzeugung : Unter<String> u = new Unter<String>( ) ; class Ober<T> { T t ; } class Unter<T> extends Ober<T> { } u.t = "hallo" ; // u.t ist vom Typ String Mischform : Einige O.kl.-Typparameter werden festgelegt, andere bleiben frei bestimmbar : class Ober<T1, T2> { T1 t1; T2 t2; } Unter<String> u = new Unter<String>( ) ; class Unter<T1> extends Ober<T1, Konto> { } u.t1 = "hallo" ; u.t2 = new Giro( ) ; // u.t1 ist vom Typ String // u.t2 ist vom Typ Konto Für Unterklasse muss mindestens ebenso starke Typeinschränkung gelten wie für Oberklasse : © K.G. Deck , H.Neuendorf class Ober<T extends Konto> { T t ; } class Unter<T extends Giro> extends Ober<T> { } // ok ! class Unter<T> extends Ober<T> { } // Fehler ! Programmieren 2 - H.Neuendorf (31) Generics – Interfaces Regeln (s.o.) gelten grundsätzlich auch für Interfaces : Erben eines generischen Interfaces von anderen generischen Interfaces + Implementieren generischer Interfaces Einschränkungen : Ein generisches Interface kann von einer Klasse nur einmal implementiert werden Nicht mehrfach für unterschiedliche Typ-Belegungen : interface IF<T> { } class Unter implements IF<Konto>, IF<Mitarbeiter> { … } // Fehler !! © K.G. Deck , H.Neuendorf Programmieren 2 - H.Neuendorf (32) Generics Wildcards (Platzhalter) "?" unbounded : ? ? extends ? super © K.G. Deck , H.Neuendorf Programmieren 2 - H.Neuendorf (33) Generics – Wildcards Parametrisierung von Methoden ist nicht-trivial - grundsätzliches Problem : Auch wenn B Untertyp von A ist, so ist dennoch nicht <B> Untertyp von <A> ! class GenPaar<T> { private T eins ; private T zwei ; public GenPaar( T e, T z ) { eins = e ; zwei = z ; } Zwar ist Girokonto kompatibel zu Konto, aber GenPaar<Girokonto> ist nicht kompatibel zu GenPaar<Konto> // … } class GenPaarTest { public static void ausgabe( GenPaar<Konto> p ) { /* … */ } public static void main( String[] args ) { Girokonto g1 = new Girokonto( 4711 ) ; Girokonto g2 = new Girokonto( 5678 ) ; GenPaar<Girokonto> p1 = new GenPaar<Girokonto>( g1, g2 ) ; ausgabe( p1 ) ; } } © K.G. Deck , H.Neuendorf // Compilerfehler !! The method ausgabe(GenPaar<Konto>) in the type GenPaar<T> is not applicable for the arguments (GenPaar<Girokonto>) Programmieren 2 - H.Neuendorf (34) Generics – Wildcards "?" unbounded wildcard, unspecified type Lösung: Methoden-Parametrisierung mit Wildcard-Symbol Klasse<?> Dadurch kann sie mit allen zulässigen Ausprägungen von GenPaar arbeiten. class GPTest { public static void ausgabe( GenPaar<?> p ) { /* … */ } public static void main( String[] args ) { Girokonto g1 = new Girokonto( 4711 ) ; Girokonto g2 = new Girokonto( 5678 ) ; GenPaar<Girokonto> p1 = new GenPaar<Girokonto>( g1, g2 ) ; ausgabe( p1 ) ; // ok ! Symbol ? steht für die Typisierung mit einer unbekannten Klasse, die in Einklang mit den Vorgaben von GenPaar ist. Wenn ? in einer Klasse mehrfach vorkommt (mehrfach in einer Methode oder in verschiedenen Methoden), dann kann es an den verschiedenen Stelle durch unterschiedliche aktuelle Typen belegt werden void test( GenPaar<?> p1, GenPaar<?> p2 ) { } } } Wildcards benötigen immer umgebenden Klassen-Typ. Können nicht für sich alleine stehen : © K.G. Deck , H.Neuendorf void test( <?> p ) { } // Fehler ! Programmieren 2 - H.Neuendorf (35) Generics – Upper Bounded Wildcards ? extends … Durch Wildcard parametrisierte Typen lassen sich auf eine Typ-Teilmenge einschränken Nur Typisierung mit dem geforderten Typ oder dessen Subtypen ist zugelassen class GenPaar<T > { private T eins ; Bei bounded wildcards ist "&" nicht verwendbar ! private T zwei ; public GenPaar( T e, T z ) { eins = e ; zwei = z ; } Nur single bound möglich! GenPaar< ? extends Girokonto & Cloneable > // … } class GenPaarTest { public static void ausgabe( GenPaar<? extends Girokonto> p ) { System.out.println( p ) ; } public static void main( String[] args ) { Girokonto g1 = new Girokonto( 4711 ) ; Girokonto g2 = new Girokonto( 5678 ) ; An Methode ausgabe() können nunmehr nur noch GenPaarInstanzen übergeben werden, die mit dem Typ Girokonto oder dessen Untertypen typisiert wurden ! Bsp: Mit Juniorkonto als Subtyp von Girokonto GenPaar<Girokonto> p1 = new GenPaar<Girokonto>( g1, g2 ) ; ausgabe( p1 ) ; // ok ! } } © K.G. Deck , H.Neuendorf Programmieren 2 - H.Neuendorf (36) Generics – Lower Bounded Wildcards ? super … Durch Wildcard parametrisierte Typen lassen sich auf eine Typ-Teilmenge einschränken Nur Typisierung mit dem geforderten Typ oder dessen Supertypen ist zugelassen class GenPaarTest { public static void ausgabe( GenPaar<? super Girokonto> p ) { Entweder upper bound oder lower bound bei Methoden. Nicht beides glechzeitig : GenPaar<? super Girokonto extends Konto> System.out.println( p ) ; } public static void main( String[] args ) { Girokonto g1 = new Girokonto( 4711 ) ; Girokonto g2 = new Girokonto( 5678 ) ; GenPaar<Girokonto> p1 = new GenPaar<Girokonto>( g1, g2 ) ; ausgabe( p1 ) ; // ok ! Juniorkonto j1 = new Juniorkonto(4711); Juniorkonto j2 = new Juniorkonto(5678); An Methode ausgabe() können nunmehr nur noch GenPaarInstanzen übergeben werden, die mit dem Typ Girokonto oder dessen Obertypen typisiert wurden ! Bsp: Juniorkonto als Subtyp von Girokonto wäre nun nicht mehr zulässig – dafür wäre hier der Obertyp Konto zugelassen ! GenPaar<Juniorkonto> p2 = new GenPaar<Juniorkonto>( j1, j2 ) ; ausgabe( p2 ) ; // Fehler ! } } © K.G. Deck , H.Neuendorf The method ausgabe(GenPaar<? super Girokonto>) in the type GenPaar<T> is not applicable for the arguments (GenPaar<Juniorkonto>) Programmieren 2 - H.Neuendorf (37) Generics – Wildcards - Verwendungs-Regeln Typische Verwendung bei Datenbehältern - auch im Collection Framework : Get aus src ? extends T Put in dest ? super T ∞ class Test<T> { public void copy( List< ? extends T> src , List<? super T> dest ) { for( int i=0; i<src.size( ); i++ ) dest.add( src.get( i ) ) ; T src dest T ∞ } } List<Konto> ldest = new ArrayList<Konto>( ) ; List<Junior> lsrc = new ArrayList<Junior>( ) ; // … tObj.copy( lsrc, ldest ) ; Aufgrund Nicht-Kovarianz wäre dann nur exakt identischer Typ von lsrc und ldest erlaubt ... // Type-inference Beim Kopieren müssen wir nicht exakt den gleichen Klassentyp fordern - wäre zu restriktiv Es genügt, wenn der Zieltyp ein Supertyp des Quelltyps ist In dieser Wildcard-Formulierung garantiert : ? super T ist garaniert Supertyp von ? extends T © K.G. Deck , H.Neuendorf Programmieren 2 - H.Neuendorf (42) Generics Generische Methoden Grundform Überladen Überschreiben Konstruktoren © K.G. Deck , H.Neuendorf Programmieren 2 - H.Neuendorf (43) Generics – Generische Methoden Generische Formulierung von Methoden mit eigenen lokalen generischen Typ-Variablen Typ-Parameter im Methodenkopf als Rückgabewerte und / oder Parameter einsetzbar Deklaration des Typ-Parameters unmittelbar vor Rückgabetyp class Test { public static <T> boolean istGleich( T p1, T p2 ) { return p1.equals( p2 ) ; } public static <T> T tuWas( T arg ) { return arg ; } Eine statische Methode kann eine generische Methode sein mit lokalen Typ-Variablen Aber statische Methoden können nicht auf globale Typ-Variablen ihrer Klasse zugreifen ! } Eine generische Methode kann in einer nicht-generischen Klasse existieren Typ-Variablen einer Methode sind lokal zu dieser. Typ-Variablen von Methoden verdecken gleichnamige Typ-Variablen der Klasse Upper bound <T extends ...> möglich © K.G. Deck , H.Neuendorf Programmieren 2 - H.Neuendorf (44) Generics – Generische Methoden Generische Formulierung von Methoden mit eigenen lokalen generischen Typ-Variablen class Test { public static <T> boolean istGleich( T p1, T p2 ) { // … return p1.equals( p2 ) ; } public static void main( String[] args ) { // Explizit parametrisierter Methoden-Aufruf : boolean b1 = Test.<String>istGleich( "ja", "nein" ) ; // Aufruf-Typ bestimmt Typ von T - Type-Inference : boolean b2 = istGleich( "hallo", "HALLO" ) ; boolean b3 = istGleich( new Double(3), new Double(3.0) ) ; } } Parametrisierte Methodenaufrufe immer in "dotted form" : Statisch Methode : Klasse. Nicht-Statische Methode: this. © K.G. Deck , H.Neuendorf super. objName. Programmieren 2 - H.Neuendorf (45) Generics – Überladen Generischer Methoden Regel : Unterschiedliche Methoden-Parameter – spezialisiert auf Typ-Parameter : Unterschiedliche Zahl von Typ-Parametern oder unterschiedliche Bounds Erasure der Signaturen müssen sich unterscheiden ! class Base<T> { // überladene Methoden - ok public void m( int x ) { /* ... */ } Bildung der Erasure ist Leistung des Compilers Erasure der Methodensignatur : public void m( int x ) { /* ... */ } public void m( T t ) { /* ... */ } public void m( Object t ) { /* ... */ } public void m( String s ) { /* ... */ } public <N extends Number> void m( N n ) { /* ... */ } } // Diese zusätzlichen Methoden wären nicht zulässig : public void m( String s ) { /* ... */ } public void m( Number n ) { /* ... */ } Erasure der zusätzlichen Methoden : public void m( Object o ) { /* ... */ } public void m( Object t ) { /* ... */ } public <G extends String> void m( G g ) { /* ... */ } public void m( String g ) { /* ... */ } public void m( Number n ) { /* ... */ } public void m( Number n ) { /* ... */ } Erasures sind identisch Kein korrektes Überladen da identische relevante Methoden-Parameter ! Compiler : Method m(T) has the same erasure m(Object) as another method in type Base<T> © K.G. Deck , H.Neuendorf Programmieren 2 - H.Neuendorf (48) Generics – Überschreiben Generischer Methoden Regel : Gleiche Methoden-Signatur – spezialisiert auf Typ-Parameter : Gleiche Zahl von Typ-Parametern mit jeweils gleichen Bounds = override-equivalent signatures Signaturen sind exakt identisch – oder Erasure der Signaturen sind identisch ! class Base<T> { Erasure der Oberklassen-Signaturen : public void m1( T t ) { /* ... */ } public <G extends String> void m2( G g ) { /* ... */ } public <N extends Number> void m3( N n ) { /* ... */ } } class Sub<T> extends Base<T>{ // überschreibt - ok public void m1( Object t ) { /* ... */ } public void m2( String g ) { /* ... */ } public void m3( Number n ) { /* ... */ } Erasure der Unterklassen-Signaturen : public void m1( Object t ) { /* ... */ } public void m1( Object t ) { /* ... */ } public void m2( String s ) { /* ... */ } public void m2( String s ) { /* ... */ } public void m3( Number n ) { /* ... */ } public void m3( Number n ) { /* ... */ } } Speziell : Methode ohne generischen Typ kann Methode mit generischem Typ überschreiben – aber nicht umgekehrt. Man kann beim Überschreiben Generizität entfernen, aber nicht hinzufügen ! Überschreiben nicht in umgekehrter Weise – sonst Compiler-Fehler © K.G. Deck , H.Neuendorf Erasures sind identisch Korrekt überschieben (nicht überladen), dh unter Einhaltung der relevanten MethodenSignatur Programmieren 2 - H.Neuendorf (49) Generics – Generische Methoden - Konstruktoren Können Typ-Parameter ihrer generischen Klasse nutzen Bei Objekterzeugung sollte Typ beidseitig spezifiziert oder <> gesetzt werden : class Gen<T> { // gen. Kl. // Objekterzeugung : private T meins ; Gen<String> g1 = new Gen( "Hallo" ) ; // Warnung public Gen( T t ) { meins = t ; } Gen<String> g2 = new Gen<>( "Hallo" ) ; // Java 7 - ok Gen<String> g3 = new Gen<String>( "Hallo" ) ; } // ok Können eigene lokale Typ-Parameter deklarieren – völlig analog zu sonstigen Methoden Typ-Inference funktioniert, ausdrückliche Typ-Nennung (nach new) jedoch erlaubt : class GenK { // nicht gen. Kl. public <T>GenK( T t ) { System.out.println( t ) ; } } // Objekterzeugung : GenK g1 = new GenK( "Hallo" ) ; // ok – Inference ! GenK g2 = new<Double> GenK( new Double(2.5) ) ; Unterschiedliche Anordnung Typ-Spezifikation : Generische Klasse versus Generische Methode © K.G. Deck , H.Neuendorf Programmieren 2 - H.Neuendorf (50) Generics – Generische Methoden - Konstruktoren Aufrufen anderer Konstruktoren mit this() und super() ist möglich Typisierung muss bei generischer Klasse in sich konsistent sein : // Klasse selbst ist generisch : // Konstruktoren als generische Methoden : // Globale Typen T und N // Lokale Typen T und N – nicht aufeinander bezogen class Gen<T,N> { class GenK { public Gen( T t ) { public <T>GenK( T t ) { System.out.println( t ) ; System.out.println( t ) ; } } public Gen( N n, T t ) { public <N,T>GenK( N n, T t ) { this( t ) ; // nicht : this( n ) this( t ) ; System.out.println( n ) ; System.out.println( n ) ; } } // oder : this( n ) da lokale Typen } } // Objekterzeugung : // Objekterzeugung : Gen<String, Integer> g1 = GenK g2 = new GenK( "hallo", new Integer(5) ) ; new Gen<String, Integer>( "hallo", new Integer(5) ) ; © K.G. Deck , H.Neuendorf Type-Inference GenK g3 = new<String, String> GenK( "Hi", "Ho" ) ; Programmieren 2 - H.Neuendorf (51)