Programmierkurs Java Teil Objektorientierte Programmierung Unterrichtseinheit 38 Generics Dr. Dietrich Boles Programmierkurs Java UE 38 Generics Dietrich Boles Seite 1 Gliederung Generics Motivation Generische Klassen Klassendefinition Objekte / Objektvariablen Übersetzung Typ-Kompatibilität Generische JDK-Klassen Iterator Vector Beispiel Typ-Parameter mit Einschränkungen Vererbung Interfaces Arrays Wildcards new static Generische Methoden Motivation Beispiele Zusammenfassung Programmierkurs Java UE 38 Generics Dietrich Boles Seite 2 Motivation / Problem Simulation eines Getränkehandels Quelle: J. Nowak. Fortgeschrittene Programmierung mit Java 5,dpunkt. Gegeben: vielerlei Getränke Drink Wine Beer WhiteWine RedWine Gewünscht: typisierte Flaschen, die nur mit Bier, Rotwein oder Weißwein gefüllt werden können Programmierkurs Java UE 38 Generics Dietrich Boles Seite 3 Motivation / Ausgangslage abstract class Drink { } class Beer extends Drink { private String brewery; public Beer(String brewery) public String getBrewery() public String toString() { this.brewery = brewery; } { return this.brewery; } { return this.getClass().getName() + "[" + this.brewery + "]“; } } abstract class Wine extends Drink { private String region; public Wine(String region) { this. region = region; } public String getRegion() { return this.region; } public String toString() { return this.getClass().getName() + "[" + this.region + "]"; } } class WhiteWine extends Wine { public WhiteWine(String region) } { super(region); } class RedWine extends Wine { public RedWine(String region) } { super(region); } Programmierkurs Java UE 38 Generics Dietrich Boles Seite 4 Motivation / erste Lösung class Bottle { private Drink content; public boolean isEmpty() { return this.content == null; } public void fill(Drink content) { this.content = content; } public Drink empty() { Lösung: polymorphe Klasse Drink content = this.content; Probleme: this.content = null; - keine korrekte Lösung return content; - Typ-Unsicherheit } } ... Bottle beerBottle = new Bottle(); beerBottle.fill(new WhiteWine("Burgunder")); ... Beer beer = (Beer)beerBottle.empty(); // ClassCastException! Programmierkurs Java UE 38 Generics Dietrich Boles Seite 5 Motivation / zweite Lösung abstract class DrinkBottle {} class BeerBottle extends DrinkBottle { private Beer cont; public void fill(Beer content) { this.cont = content; } public Beer empty() { Beer c=this.cont; this.cont=null; return c; } } abstract class WineBottle extends DrinkBottle {} class WhiteWineBottle extends WineBottle { private WhiteWine cont; public void fill(WhiteWine content) { this.cont = content; } public WhiteWine empty() { WhiteWine c=this.cont; this.cont=null; return c; } } class RedWineBottle extends WineBottle { private RedWine cont; public void fill(RedWine content) { this.cont = content; } public RedWine empty() { RedWine c = this.cont; this.cont = null; return c; } } Lösung: Problem: Getränk-spezifische Flaschen Keine Vererbung, keine wirkliche Polymorphie! Programmierkurs Java UE 38 Generics Dietrich Boles Seite 6 Motivation / dritte Lösung (1) class Bottle<T> { private T content; public boolean isEmpty() { return this.content == null; } public void fill (T content) { this.content = content; } public T empty() { T content = this.content; this.content = null; return content; } } Lösung: - Generische (parametrisierte) Klasse - T ist formaler Typ-Parameter (Typ-Variable) der Klasse Bottle Programmierkurs Java UE 38 Generics Dietrich Boles Seite 7 Motivation / dritte Lösung (2) Bottle<Beer> beerBottle = new Bottle<Beer>(); Bottle<WhiteWine> whiteWineBottle = new Bottle<WhiteWine>(); Aktueller Typ-Parameter (Klasse) beerBottle.fill(new Beer("Veltins")); beerBottle.fill(new WhiteWine("Burgunder")); // Fehlermeldung durch Compiler! whiteWineBottle.fill(new WhiteWine("Burgunder")); Beer beer = beerBottle.empty (); System.out.println (beer); WhiteWine whiteWine = whiteWineBottle.empty (); System.out.println (whiteWine); Programmierkurs Java UE 38 Generics Dietrich Boles Seite 8 Generische Klassen / Klassendefinition class C<T> { ... } T kann im weiteren Klassenkopf und im Klassenrumpf (fast) überall da verwendet werden, wo Klassennamen stehen können class D<T> extends T { } class E<T> { T obj; } interface I<T1, T2> { public void setze(T1 obj1, T2 obj2); } class F<T4, T5, T6> implements I<T4, T6> { public T5 liefere() { ...} public void setze(T4 obj1, T6 obj2) { ... } } Programmierkurs Java UE 38 Generics Dietrich Boles Seite 9 Generische Klassen / Objekte und Objektvariablen class C<T> { Formaler Typ-Parameter public T f(T t) {...} } class A { } class B { } Parametrisierte Klasse / Typ C<A> obj = new C<A>(); Aktueller Typ-Parameter (Klasse, kein Standarddatentyp) C<B> obj2 = new C<B>(); A a = obj.f(new A()); B b = obj2.f(new B()); A a2 = obj.f(new B()); // Fehlermeldung durch Compiler Programmierkurs Java UE 38 Generics Dietrich Boles Seite 10 Generische Klassen / Übersetzung class C<T> T t; T f(T t) T t1 = String return } } { { t; this.t = t1; str = t.toString(); this.t; Compiler class C { Object t; Object f(Object t) { Object t1 = t; this.t = t1; String str = t.toString(); return this.t; } } Raw-Type Daher erlaubt: C obj = new C(); + Meta-Informationen! Programmierkurs Java UE 38 Generics Dietrich Boles Seite 11 Generische Klassen / Typ-Kompatibilität class class class class C<T> { ... } A { } B { } D extends A { } C<B> C<A> C<D> C<A> C<A> C<Object> // C<A> obj = new C<B>(); Fehler // C<A> obj = new C<D>(); Fehler // C<Object> = new C<A>(); Fehler C<A> Object // Object obj = new C<A>(); ok Programmierkurs Java UE 38 Generics Dietrich Boles Seite 12 Generische JDK-Klassen / java.util.Iterator public interface Iterator<T> { public boolean hasNext(); public T next(); } public interface Iterable<T> { // in java.lang public java.util.Iterator<T> iterator(); } Programmierkurs Java UE 38 Generics Dietrich Boles Seite 13 Generische JDK-Klassen / java.util.Vector public class Vector<T> extends java.util.AbstractList<T> implements Iterable<T>, ... { public Vector(); public Vector(int initCapacity); public boolean add(T obj); public boolean contains(Object obj); public T elementAt(int index); public T get(int index); public void insertElementAt(T o,int i); public boolean remove(Object obj); public int size(); public java.util.Iterator<T> iterator(); ... } Programmierkurs Java UE 38 Generics Dietrich Boles Seite 14 Generische JDK-Klassen / Beispiel import java.util.Vector; import java.util.Iterator; public class IteratorBspMitCast { public static void main(String[] args) { Vector speicher = new Vector(); speicher.add(4711); speicher.add(46); speicher.add(33); Raw-Type int summe = 0; Iterator iter = speicher.iterator(); while (iter.hasNext()) { summe += (Integer)iter.next(); } Cast notwendig! System.out.println(summe); } Compiler-Warnung: ... uses unsafe operations } Programmierkurs Java UE 38 Generics Dietrich Boles Seite 15 Generische JDK-Klassen / Beispiel import java.util.Vector; import java.util.Iterator; public class IteratorBsp { public static void main(String[] args) { Vector<Integer> speicher = new Vector<Integer>(); speicher.add(4711); speicher.add(46); speicher.add(33); int summe = 0; Iterator<Integer> iter = speicher.iterator(); while (iter.hasNext()) { summe += iter.next(); Kein Cast notwendig! } System.out.println(summe); } } Programmierkurs Java UE 38 Generics Dietrich Boles Seite 16 Generische JDK-Klassen / Beispiel import java.util.Vector; public class IteratorBspFor { public static void main(String[] args) { Vector<Integer> speicher = new Vector<Integer>(); speicher.add(4711); speicher.add(46); speicher.add(33); int summe = 0; for (Integer i : speicher) { summe += i; } neue for-Schleife System.out.println(summe); } } Programmierkurs Java UE 38 Generics Dietrich Boles Seite 17 Typ-Parameter mit Einschränkungen Motivationsproblem: Bottle<Object> ist keine Getränkeflasche! Lösung: Einschränkung des Typ-Parameters class Bottle<T extends Drink> { /* wie auf Folie 7 */ } class Petrol { } class Stout extends Beer { ... } Bottle<Drink> drinkBottle = new Bottle<Drink>(); Bottle<Beer> beerBottle = new Bottle<Beer>(); Bottle<Object> objectBottle = new Bottle<Object>(); // Fehlermeldung durch Compiler Bottle<Petrol> petrolBottle = new Bottle<Petrol>(); // Fehlermeldung durch Compiler Bottle<Stout> stoutBottle = new Bottle<Stout>(); Programmierkurs Java UE 38 Generics Dietrich Boles Seite 18 Typ-Parameter mit Einschränkungen / Übersetzung class A { public void doIt() {...} } class C<T extends A> { T t; T f(T t) { T t1 = t; this.t = t1; this.t.doIt(); return this.t; } } Programmierkurs Java Compiler UE 38 Generics class C { A t; A f(A t) { A t1 = t; this.t = t1; this.t.doIt(); return this.t; } } Dietrich Boles Seite 19 Vererbung class A { } class B<T extends A> { public void f(T t) { ... } } class C<T> extends A { } class D<T extends A> extends B<T> { } class E extends B<A> { public void f(A obj) { ... } public void f(Object obj) { ... } } Programmierkurs Java UE 38 // Überschreiben // Überladen Generics Dietrich Boles Seite 20 Interfaces class A { } class B { } interface I<T1 extends A, T2 extends B> { public void f1(T1 t); public void f2(T2 t); } class C implements I<A, B> { public void f1(A t) { ... } public void f2(B t) { ... } } class D implements I<A, A>, I<B, B> { ... } // Fehler: dasselbe Interface nicht zweimal implementieren Programmierkurs Java UE 38 Generics Dietrich Boles Seite 21 Arrays mit parametrisierten Klassen (1) Motivationsproblem: Klasse für Getränke-spezifische Getränkekästen Problem: Arrays mit parametrisierten Klassen sind nicht erlaubt Lösung: class BottleBox<T extends Drink> { private Object[] bottles; private int count = 0; public BottleBox(int number) { // this.bottles = new T[number]; nicht erlaubt this.bottles = new Object[number]; } public void add(Bottle<T> bottle) { this.bottles[this.count] = bottle; this.count++; } public Bottle<T> getBottle(int index) { return (Bottle<T>)this.bottles[index]; } } Internes sicheres (!) Cast Programmierkurs Java UE 38 Generics Dietrich Boles Seite 22 Arrays mit parametrisierten Klassen (2) BottleBox<Beer> beerBottleBox = new BottleBox<Beer>(6); // Bierkasten füllen for (int i = 0; i < 6; i++) { Bottle<Beer> beerBottle = new Bottle<Beer>(); beerBottle.fill(new Beer("Jever")); beerBottleBox.add(beerBottle); } Bottle<RedWine> wineBottle = new Bottle<RedWine>(); beerBottleBox.add(wineBottle); // Fehlermeldung ... // Bierkasten leeren for (int i = 0; i < 6; i++) { Bottle<Beer> beerBottle = beerBottleBox.getBottle(i); Beer beer = beerBottle.empty (); System.out.println(beer); } Programmierkurs Java UE 38 Generics Dietrich Boles Seite 23 Wildcards (1) Motivationsproblem: Klasse für Getränkekästen mit beliebigen Flaschen Lösung: „Wildcards“ class BottleBox { private Object[] bottles; private int count = 0; public BottleBox(int number) { this.bottles = new Object[number]; } public void add(Bottle<? extends Drink> bottle) { this.bottles[this.count] = bottle; this.count++; } public Bottle<? extends Drink> getBottle(int index) { return (Bottle<? extends Drink>)this.bottles[index]; } } Programmierkurs Java UE 38 Generics Dietrich Boles Seite 24 Wildcards (2) BottleBox box = new BottleBox(6); // Füllen Bottle<Beer> beerBottle = new Bottle<Beer>(); beerBottle.fill(new Beer("Veltins")); box.add (beerBottle); Bottle<WhiteWine> whiteWineBottle = new Bottle<WhiteWine>(); whiteWineBottle.fill (new WhiteWine("Burgunder")); box.add (whiteWineBottle); // Leeren for (int i = 0; i < 6; i++) { Bottle<? extends Drink> bottle = box.getBottle (i); Drink drink = bottle.empty (); System.out.println(drink); } Programmierkurs Java UE 38 Generics Dietrich Boles Seite 25 Wildcards (3) class A { } class B { } class C<T> { } C<A> ca = new C<A>(); // ok C<Object> cobj = new C<A>(); // Fehler C obj = new C<A>(); // ok, aber möglichst vermeiden C<?> c1 = new C<A>(); C<?> c2 = new C<B>(); // ok // ok ? steht für irgendeinen Typ Programmierkurs Java UE 38 Generics Dietrich Boles Seite 26 Wildcards (4) Motivationsproblem: Klasse für Getränke-spezifische Getränkekästen Problem der Lösung auf Folie 22: BottleBox<Beer> Lösung: Semantische Ungereimtheit class BottleBox<T extends Bottle<? extends Drink>> { private Object[] bottles; private int count = 0; public BottleBox(int number) { this.bottles = new Object[number]; } public void add(T bottle) { this.bottles[this.count] = bottle; this.count++; } public T getBottle(int index) { return (T)this.bottles[index]; } } Programmierkurs Java UE 38 Generics Dietrich Boles Seite 27 Wildcards (5) BottleBox<Bottle<Beer>> beerBottleBox = new BottleBox<Bottle<Beer>>(6); // Bierkasten füllen for (int i = 0; i < 6; i++) { Bottle<Beer> beerBottle = new Bottle<Beer>(); beerBottle.fill(new Beer("Jever")); beerBottleBox.add(beerBottle); } ... // Bierkasten leeren for (int i = 0; i < 6; i++) { Bottle<Beer> beerBottle = beerBottleBox.getBottle(i); Beer beer = beerBottle.empty (); System.out.println(beer); } Programmierkurs Java UE 38 Generics Dietrich Boles Seite 28 new class A { } class C<T> { T t = new T(); // Fehlermeldung; Grund: Übersetzung // von T nach Object } Programmierkurs Java UE 38 Generics Dietrich Boles Seite 29 static class A { } Class B { } class C<T> { static T t; // Fehlermeldung static C<T> c; // Fehlermeldung } C<A> ca = new C<A>(); C<B> cb = new C<B>(); Grund: Der Compiler generiert nur eine (!) Klasse. Beide Objekte würden sich die statischen Attribute teilen. Programmierkurs Java UE 38 Generics Dietrich Boles Seite 30 Generische Methoden / Motivation (1) Motivationsproblem: Umfüllen von Flaschen Mögliche Lösung: class BottleTransfuser<T extends Drink> { void transfuse(Bottle<T> fromBottle, Bottle<T> toBottle) { T drink = fromBottle.empty(); toBottle.fill(drink); } } BottleTransfuser<Beer> beerTransfuser = new BottleTransfuser<Beer>(); ... Problem: beerTransfuser kann nur Bierflaschen umfüllen! Programmierkurs Java UE 38 Generics Dietrich Boles Seite 31 Generische Methoden / Motivation (2) allgemeiner Lösung: Generische Methoden class BottleTransfuser { <T extends Drink> void transfuse (Bottle<T> fromBottle, Bottle<T> toBottle) { T drink = fromBottle.empty(); toBottle.fill(drink); } } BottleTransfuser transfuser = new BottleTransfuser (); Bottle<Beer> b1 = new Bottle<Beer>(); b1.fill(new Beer("Jever")); Bottle<Beer> b2 = new Bottle<Beer>(); transfuser.transfuse(b1, b2); Bottle<WhiteWine> b3 = new Bottle<WhiteWine>(); b3.fill(new WhiteWine("Burgunder")); Bottle<WhiteWine> b4 = new Bottle<WhiteWine>(); transfuser.transfuse(b3, b4); transfuser.transfuse(b1, b4); // Fehlermeldung Programmierkurs Java UE 38 Generics Dietrich Boles Seite 32 Generische Methoden / Beispiele (1) class A { public void f() {} } class B extends A { } class C { public <T> void f1(T t) { String str = t.toString(); } public static <T extends A> void f2(T t) { t.f(); } } C obj = new C(); obj.f1("Hallo"); obj.f1(4711); C.f2(new A()); C.f2(new B()); C.f2("Hallo"); // Fehlermeldung Programmierkurs Java UE 38 Generics Dietrich Boles Seite 33 Generische Methoden / Beispiele (2) class C { public static <T> void f3(T t1, T t2) { } public static <T> T f4(T t1, T t2) { return Math.random() < 0.5 ? t1 : t2; } Die "T" müssen } einen gemeinsamen Supertyp haben C.f3(11, "Hallo"); // Supertyp Object C.f3("Hallo", 11); // Supertyp Object String s2 = C.f4("Hallo", "World"); // Supertyp String String s1 = C.f4(11, "Hallo"); // Fehlermeldung Comparable c1 = C.f4(11, "World"); // Supertyp Comparable Programmierkurs Java UE 38 Generics Dietrich Boles Seite 34 Zusammenfassung Generics: Parametrisierte Klassen Sinn und Zweck: Semantische Korrektheit Vermeidung von Type-Casts Vermeidung von ClassCast-Exceptions Programmierkurs Java UE 38 Generics Dietrich Boles Seite 35