Generics - DHBW Mosbach

Werbung
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)
Herunterladen