1 / 22 Einstieg in die Informatik mit Java Generics Gerd Bohlender Institut für Angewandte und Numerische Mathematik
Gliederung 2 / 22 1 Überblick Generics 2 Generische Klassen 3 Generische Methoden 4 Ergänzungen
Gliederung 3 / 22 1 Überblick Generics 2 Generische Klassen 3 Generische Methoden 4 Ergänzungen
Überblick Generics Java besitzt seit Version 5 generische Klassen und Methoden. Ohne Generics Verwendung von Object In diesen Fällen ist keine Typprüfung möglich Mit Generics Angabe eines generischen Typs Typprüfung möglich 4 / 22
Gliederung 5 / 22 1 Überblick Generics 2 Generische Klassen 3 Generische Methoden 4 Ergänzungen
Programmierung ohne Generische Klasse 6 / 22 class Speicher { Object o b j e c t ; void speichern ( Object obj ) { o b j e c t = obj ; Object laden ( ) { return o b j e c t ; / / Speicher erzeugen, s p e i c h e r t a l l e s ohne Pruefung Speicher sp = new Speicher ( ) ; / / etwas speichern... sp. speichern ( Hallo ) ; / /... und wieder laden, Typecast noetig! S t r i n g s t = ( S t r i n g ) sp. laden ( ) ; / / der folgende Fehler wird e r s t zur L a u f z e i t entdeckt I n t e g e r i = ( I n t e g e r ) sp. laden ( ) ; / / f a l s c h e r Typ!
Definition einer Generischen Klasse Bei der Definition einer generischen Klasse wird ein formaler Typparameter angegeben. Dieser steht für eine beliebige Klasse, ein Interface oder einen anderen Typparameter. Grundtypen sind nicht erlaubt. Syntax class Klassenname <T> { // Verwendung des Typparameters T Falls mehrere Typparameter benötigt werden, werden sie mit Komma getrennt. In dem Fall müssen verschiedene Bezeichner verwendet werden. Typparameter erhalten üblicherweise einbuchstabige Bezeichner mit einem Großbuchstaben. Übliche Bezeichner: T (steht für Type), E (Element), K (Key), N (Number), V (Value), S, U, V,... (zweiter und weitere Typparameter) 7 / 22
8 / 22 Definition einer Generischen Klasse Der Typparameter T darf bei der Definition der generischen Klasse u.a. verwendet werden als Typ von Instanzvariablen, als Typ formaler Parameter von Instanzmethoden oder Konstruktoren der Klasse, als Ergebnistyp von Instanzmethoden, als lokale Variable in Instanzmethoden oder Konstruktoren, in Initialisierern. Dies gilt ebenso für den Feldtyp T[]. Hierbei kann jeweils der aktuelle Typ für den formalen Typparameter T beim Compilieren aus den Angaben bei der jeweiligen Instanz bzw. aus dem aktuellen Parameter der Methode / des Konstruktors bestimmt werden.
Definition einer Generischen Klasse Nicht erlaubt ist die Verwendung eines Typparameters T in einer static-umgebung, also: Klassenvariablen vom Typ T, Klassenmethoden mit Argumenttyp, lokalen Variablen oder Ergebnistyp T, Auftreten von T in Klasseninitialisierer. Der Grund hierfür ist, dass der Typparameter T für eine nicht-statische Klasse steht. Nicht erlaubt sind außerdem mit einem Typparameter T Konstruktoraufrufe new T(), die Erzeugung von Feldern new T[3], Typecasts (T)new Object(), Aufrufe von instanceof T zum Laufzeittest des Typs. Der Grund hierfür ist, dass der Typ T in der generischen Klasse nicht abgespeichert wird (der Typparameter T wird beim Übersetzen gelöscht: Type Erasure ). Die Klasse hat hier also keine Möglichkeit, den aktuellen Typ zu bestimmen. 9 / 22
Verwendung einer Generischen Klasse Bei der Verwendung der generischen Klasse wird ein Typ angegeben. Die Klasse ist damit auf diesen Typ (und ggf. Subklassen) festgelegt. Syntax Klassenname <Typangaben> Hierbei gelten die folgenden Regeln: Die Typangaben sind eine durch Komma getrennte Liste von Klassen (bzw. Interfaces oder Typparametern, die indirekt auf eine Klasse führen). Die Anzahl der Klassen muss mit der Anzahl der Typparameter übereinstimmen, sie werden in der angegebenen Reihenfolge zugeordnet. Eine Typangabe darf mehrfach vorkommen, sie wird dann mehreren Typparametern zugeordnet. Mit der Definition class Demo<T,U>{ ist z.b. erlaubt Demo<Integer,Integer>. 10 / 22
11 / 22 Programmierung mit Generischer Klasse Weitere Regeln bei der Verwendung generischer Klassen: Die angegenenen Typen werden nur bei der Compilierung zur Typprüfung verwendet. Das class-file enthält keine Angaben über die generischen Typangaben ( Type Erasure ). Es wird nur ein class-file für eine generische Klasse erzeugt, also nicht für jede vorkommende Typangabe eine spezifische Variante (im Gegensatz z.b. zu template-klassen in C++). Zur Kompatibilität mit älteren Programmen (vor Java 5) kann <Typangabe> auch entfallen. In dem Fall wird Object als Typ angenommen. Mit der Definition class Demo<T>{ ist z.b. erlaubt Demo d = new Demo(); Eine solche Mischung von generischem und nicht-generischem Code sollte möglichst vermieden werden!
Programmierung mit Generischer Klasse 12 / 22 class Speicher<T> { T o b j e c t ; void speichern ( T obj ) { o b j e c t = obj ; T laden ( ) { return o b j e c t ; / / Speicher erzeugen, s p e i c h e r t nur S t r i n g s Speicher<String > sp = new Speicher<String > ( ) ; / / etwas speichern... sp. speichern ( Hallo ) ; / /... und wieder laden, kein Typecast n o e t i g! S t r i n g s t = sp. laden ( ) ; / / der folgende Fehler wird vom Compiler entdeckt I n t e g e r i = ( I n t e g e r ) sp. laden ( ) ; / / f a l s c h e r Typ!
Programmierung mit Generischer Klasse 13 / 22 Die Verwendung von Subklassen ist erlaubt, z.b. bei Hüllklassen: / / Speicher f ü r b e l i e b i g e Zahltypen Speicher<Number> sn = new Speicher<Number> ( ) ; sn. speichern (new I n t e g e r ( 1 ) ) ; / / I n t e g e r sn. speichern ( 1 ) ; / / ebenso, automatische Konversion sn. speichern (new Double ( 1. 2 3 ) ) ; / / Double sn. speichern ( 1. 2 3 ) ; / / ebenso, automatische Konv. sn. speichern ( 111 ) ; / / Fehler, S t r i n g verboten Und zur Kompatibilität mit alten Programmen (vor Java 5): Speicher so = new Speicher ( ) ; / / Typ Object so. speichern ( Hallo ) ; so. speichern ( 5 5 5 ) ; System. out. p r i n t l n ( so. laden ( ) ) ; / / 555
Programmierung mit Generischer Klasse, 2 Parameter 14 / 22 Beispiel mit 2 Parametern: class Demo<T, U> { T t ; U u ; Demo ( T t, U u ) { this. t = t ; this. u = u ; public S t r i n g t o S t r i n g ( ) { return t. t o S t r i n g ( ) + = + u. t o S t r i n g ( ) ;... Demo<String, Integer > s i = new Demo<String, Integer > ( Jahr, 2010); System. out. p r i n t l n ( s i ) ; / / Jahr = 2010
Gliederung 15 / 22 1 Überblick Generics 2 Generische Klassen 3 Generische Methoden 4 Ergänzungen
16 / 22 Generische Methoden Bei Methoden und ggf. Konstruktoren sind ebenfalls generische Typparameter erlaubt. Bei der Definition der Methode: Typparameter vor dem Ergebnistyp. Bei der Verwendung einer Klassenmethode: Klassenname.<Typangabe> vor dem Methodennamen. Bei der Verwendung einer Instanzmethode: Instanzname.<Typangabe> vor dem Methodennamen. Oft kann diese Angabe entfallen und automatisch aus dem Typ der normalen aktuellen Parameter bestimmt werden.
Generische Klassenmethode 17 / 22 import java. u t i l. ; public class GenerischeFunktion { s t a t i c <T> void f u e l l e F e l d ( T wert, T [ ] f e l d ) { for ( i n t i =0; i <f e l d. l e n g t h ; i ++) f e l d [ i ] = wert ; public s t a t i c void main ( S t r i n g [ ] args ) { I n t e g e r [ ] f = new I n t e g e r [ 1 0 ] ; / / Klassenname i s t h i e r notwendig GenerischeFunktion. <Integer > f u e l l e F e l d ( 5, f ) / / Typangabe ( und dann auch Klassenname ) koennen / / o f t e n t f a l l e n, werden automatisch bestimmt f u e l l e F e l d ( 5, f ) ; System. out. p r i n t l n ( f [ 9 ] ) ;
Generische Instanzmethode import java. u t i l. ; public class GenerischeFunktion2 { <T> void f u e l l e F e l d ( T wert, T [ ] f e l d ) { for ( i n t i =0; i <f e l d. l e n g t h ; i ++) f e l d [ i ] = wert ; public s t a t i c void main ( S t r i n g [ ] args ) { I n t e g e r [ ] f = new I n t e g e r [ 1 0 ] ; GenerischeFunktion2 t f 2 = new GenerischeFunktion2 ( ) ; / / Instanzname i s t h i e r notwendig t f 2. <Integer > f u e l l e F e l d ( 5, f ) ; / / Typangabe ( aber n i c h t der Instanzname ) kann / / o f t e n t f a l l e n, wird automatisch bestimmt t f 2. f u e l l e F e l d ( 5, f ) ; System. out. p r i n t l n ( f [ 9 ] ) ; 18 / 22
Gliederung 19 / 22 1 Überblick Generics 2 Generische Klassen 3 Generische Methoden 4 Ergänzungen
Ergänzung: Beschränkung der zulässigen Klassen Falls für den Typparameter T nur Subtypen einer Superklasse Sup erlaubt sein sollen, kann dies durch class Klassenname <T extends Sup> {... angegeben werden. Die gleiche Syntax gilt auch für die Implementierung von Interfaces (es wird hier also extends und nicht implements verwendet!). Sollen mehrere Interfaces implementiert werden, werden sie nicht mit Komma sondern mit & aufgelistet (das Komma trennt Typparameter). 20 / 22
Ergänzung: Vererbung bei generischer Klasse 21 / 22 Gegeben sei eine generische Klasse class Klasse<T>... Mit einer Subklasse Sub zu einer Superklasse Super ist trotzdem Klasse<Sub> keine Subklasse zu Klasse<Super> Damit ist z.b. folgendes Programstück fehlerhaft: / / D e f i n i t i o n generische Klasse class Klasse<T> { / / okay / / automatischer Typecast : Number n = new I n t e g e r ( 1 ) ; / / okay / / kein automatischer Typecast moeglich : Klasse<Number> kn = new Klasse<Integer > ( ) ; / / f a l s c h / / auch kein e x p l i z i t e r Typecast e r l a u b t : Klasse<Number> kn = ( Klasse<Number>) new Klasse<Integer > ( ) ; / / f a l s c h
Ergänzung: Wildcard 22 / 22 Bei der Verwendung generischer Typen kann das Wildcard-Zeichen? eingesetzt werden. Hier gibt es Einschränkungen bei der Verwendung, da der Typ unbekannt ist. / / D e f i n i t i o n generische Klasse class Klasse<T> { / / okay / / der generische Typparameter i s t e i n e unbekannte / / Klasse, d i e Subklasse von Number i s t Klasse <? extends Number> k1 ; / / der generische Typparameter i s t e i n e unbekannte / / Klasse, die Subklasse von Object i s t / / ( also jede b e l i e b i g e ) Klasse<?> k2 ; / / der generische Typparameter i s t e i n e unbekannte / / Klasse, d i e Superklasse von Number i s t Klasse <? super Number> k3 ;