Generische Klassen (Generics)
Generische Klassen (Generics)
Generische (engl. generic) Klassen, Schnittstellen und Methoden wurden in Java in der Version 5.0 eingeführt. Generisch bedeutet in diesem Zusammenhang, dass die entsprechenden Klassen, Methoden und Schnittstellen parametrisierbare Typen verwenden. Ein Übergabetyp ist also nicht im Quellcode festgelegt, er kann bei der Verwendung zum Übersetzungszeitpunkt verschiedene Ausprägungen annehmen. |
Das Konzept der generischen Klassen erhöht die Typsicherheit im Zusammenhang mit Polymorphismus und Vererbung. Eine Erhöhung der Typsicherheit bedeutet, dass der Entwickler weniger explizite Casts verwenden muss um Typkonversionen und Anpassungen zu erzwingen.
Generische Klasse |
---|
Verwendet eine Klasse formale Typ-Parameter so nennt man sie generische Klasse. Der formale Typ-Parameter ist ein symbolischer Typ der wie ein normaler Bezeichner in Java aufgebaut ist. Er wird nach dem Klassennamen in spitzen Klammern angegeben. |
Am einfachsten lässt sich das Konzept an einem Beispiel einer Klasse Koordinate mit einem parametrisierbaren Typen <T> veranschaulichen:
public class Koordinate<T> { private T x; private T y; Koordinate(T p1, T p2) { x = p1; y = p2; } ... }
In dieser Implementierung der Klasse wird der eigentliche Typ der Komponenten (x und y) der Klasse Koordinate nicht konkret festgelegt. Ein Konsument dieser Klasse kann sich entscheiden diese Implementierung für Integer, Float oder Double zu verwenden. Die allgemeine Syntaxregel für die Deklaration einer generischen Klasse lautet:
Deklaration einer generischen Klasse |
---|
class Klassenname < Typvariablenliste > {//Body} |
Will man die Implementierung der generischen Klasse Koordinate für große Fließkommazahlen verwenden, so benutzt man die folgende Syntax:
Koordinate<Double> eineKoordinate = new Koordinate<Double>(2.2d, 3.3d);
Der Typ ist parametrisierbar und wird Teil des Variablennamens bzw. des Klassennamens. Die allgemeine Syntax zum Erzeugen eines Objekts einer generischen Klasse lautet:
Instanziieren einer generischen Klasse |
---|
new Klassenname < Typliste > ( Parameterliste); |
Will man kleinere Fließkommazahlen benutzen, so kann man die Klasse Koordinate auch mit dem Typ Float parametrisieren:
Koordinate<Float> nochEineKoordinate = new Koordinate<Float>(4.4f, 5.5f);
Beispiel einer einfachen generischen Klasse
Die Klasse Koordinate hat ein Hauptprogramm main() welches zwei Instanzen mit unterschiedlichen Ausprägungen erzeugt und bei der Ausgabe der Werte die Methode toString() implizit aufruft:
package s2.generics; /** * * @author s@scalingbits.com * @param <T> Generischer Typ der Klasse Koordinate */ public class Koordinate<T> { private T x; private T y; public T getX() {return x;} public void setX(T x) {this.x = x;} public T getx() {return x;} public void setY(T y) { this.y = y;} public T getY() {return y;} public Koordinate(T xp, T yp ) { x = xp; y = yp; } @Override public String toString() {return "x: " + x + "; y: " + y;} public static void main (String[] args) { Koordinate<Double> k1 = new Koordinate<Double>(2.2d, 3.3d); System.out.println(k1); Koordinate<Integer> k2 = new Koordinate<Integer>(2, 3); System.out.println(k2); Koordinate<Number> k3 = new Koordinate<Number>(4.4f, 5.5f); System.out.println(k3); } // Ende main() } // Ende Klasse Koordinate
Bei der Ausführung ergibt sich die folgende Konsolenausgabe:
x: 2.2; y: 3.3 x: 2; y: 3 x: 4.4; y: 5.5
- 12349 views
Generics zur Übersetzungs- und Laufzeit
Generics zur Übersetzungs- und LaufzeitDer javac Übersetzer erzeugt aus der gegebenen generischen Klasse in der Datei Koordinate.java nur genau eine Datei Koordinate.class mit Bytecode für alle möglichen Instanzierungen.
package s2.generics;/**
*
* @author s@scalingbits.com
* @param <T> Generischer Typ der Klasse Koordinate
*/
public class Koordinate<T> {
private T x;
private T y;public T getX() {return x;}
public void setX(T x) {this.x = x;}
public T getx() {return x;}
public void setY(T y) { this.y = y;}
public T getY() {return y;}public Koordinate(T xp, T yp ) {
x = xp;
y = yp;
}@Override
public String toString() {return "x: " + x + "; y: " + y;}public static void main (String[] args) {
Koordinate<Double> k1 = new Koordinate<Double>(2.2d, 3.3d);
System.out.println(k1);
Koordinate<Integer> k2 = new Koordinate<Integer>(2, 3);
System.out.println(k2);
Koordinate<Number> k3 = new Koordinate<Number>(4.4f, 5.5f);
System.out.println(k3);
}
}
Der erzeugte Bytecode enthält nicht mehr den Formalparameter <T>. Er enthält den Referenztyp Object. Aus diesem Grund kann der Bytecode mit allen Instanzierungen arbeiten solange sie aus der Klasse Object abgeleitet sind. Direkte Basistypen wie int oder long können daher nicht direkt in generischen Klassen verwendet werden. Die Stellvertreterklassen Integer und Long in Verbindung mit dem "Autoboxing" sind der Ersatz für die direkte Verwendung.
Zur Laufzeit kann dann der Bytecode verwendet werden um den aktuell parametrisierten Parametern zu arbeiten.
Das Ersetzen der Parametrisierung <T> durch die Klasse Object nennt man Type-Erasure (engl. Auslöschen).
Polymorphismus und Vererbung von generischen Klassen |
---|
Aufgrund der Möglichkeit die Klassen mit unterschiedlichen aktuellen Parametern zu benutzen sind generische Klassen polymorph. Die aktuell parametrisierten Objekte stehen jedoch auf der gleichen Vererbungsebene. Sie haben keine Vererbungsbeziehung! |
- 5363 views
Generics, Autoboxing, Subtyping
Generics, Autoboxing, SubtypingDie im vorhergehenden Beispiel benutzte Klasse kann aber auch als aktuellen Parameter eine abstrakte Klasse wie Number verwenden:
Mit Hilfe des Java Autoboxing kann man die Variablen k3 und k4 auch wie folgt belegen:
Koordinate<Number> k3 = new Koordinate<Number>(2l, 3l); System.out.println(k3);
k3 = new Koordinate<Number>(4.4f, 5.5f);
System.out.println(k3);
Die Variable k3 hat den formalen Parametertyp Number. Die aktuellen Parameter 21 und 31 sind int Typen. Sie werden automatisch in Instanzen von Integer umgewandelt und sind daher Spezialisierungen der Klasse Number. Die Variable k3 zeigt hier zuerst auf eine Koordinate die aus ganzen Zahlen besteht und anschließend auf eine Koordinate die aus Fließkommanzahlen bestehen
Ohne Autoboxing würde man die Variable k3 so belegen:
Koordinate<Number> k3 = new Koordinate<Number>(new Integer(2l), new Integer(3l));
Subtyping
Die parametrisierte Klasse Koordinate<Number> kann zwar wahlweise auf verschiedene Varianten von Objekten zugreifen die parametrisiert mit Koordinate<Number> erzeugt wurden. Sie kann aber nicht auf Objekte gleichen Inhalts aus Koordinate<Integer> zugreifen. Das folgende Implementierungsbeispiel erlaubt nicht die letzte Zuweisung von k2 auf k3:
Koordinate<Double> k1 = new Koordinate<Double>(2.2d, 3.3d); Koordinate<Integer> k2 = new Koordinate<Integer>(2, 3); Koordinate<Number> k3 = new Koordinate<Number>(2l, 3l); k3 = new Koordinate<Number>(4.4f, 5.5f); k3 = k2; // Fehler
Die Vererbungsbeziehung besteht nicht zwischen den generischen Klassen selbst. Der Javaübersetzer erzeugt den folgenden Fehler:
found : Kurs2.Generics.Koordinate<java.lang.Integer> required: Kurs2.Generics.Koordinate<java.lang.Number> k3 = k2;
In diesem Beispiel wird das "Liskov Substitution Principle" verletzt! Der Übersetzer javac erkennt diese Fehler und übersetzt diesen Quellcode nicht.
- 5403 views
Vererbung und generische Klassen
Vererbung und generische KlassenDie Vererbung zwischen generischen Klassen untereinander und mit nicht generischen Klassen ergibt eine zweidimensionale Tabelle von Möglichkeiten:
Oberklasse generisch | Oberklasse nicht generisch | |
---|---|---|
Unterklasse generisch | ||
Unterklasse nicht generisch |
|
Eine generische Klasse erweitert eine generische Klasse
Will man eine generische Klasse aus einer anderen generischen Klasse ableiten so gibt es zwei Möglichkeiten:
- der formale Typ-Parameter der Oberklasse wird weitervererbt
- der formale Typ-Parameter der Oberklasse wird durch einen aktuellen Parameter ersetzt.
Formaler Parameter der Oberklasse ersetzt formalen Parameter der Unterklasse
Bei dieser Form der Vererbung hat die Klassendeklaration der Unterklasse die folgende Noatation:
public class Unterklasse<T> extends Oberklasse<T>
Ein Beispiel ist die Erweiterung der zweidimensionalen Klasse Koordinate zu einer drei dimensionalen Koordinate in der Klasse Koordinate3DGen
package s2.generics;/**
*
* @author s@scalingbits.com
* @param <T> Generischer Typ der Klasse Koordinate
*/
public class Koordinate3DGen<T> extends Koordinate<T> {
private T z;public T getZ() {return z;}
public void setZ(T z) {this.z = z;}public Koordinate3DGen (T x, T y, T z) {
super (x,y);
this.z = z;
}@Override
public String toString() {return super.toString()+", z: "+ z;}public static void main (String[] args) {
Koordinate3DGen<Double> k1 = new Koordinate3DGen<Double>(1.1d, 2.2d, 3.3d);
System.out.println(k1);Koordinate3DGen<Integer> k2 = new Koordinate3DGen<Integer>(1,2,3);
System.out.println(k2);
}
}
Die z Dimension kann in dieser Klasse immer nur mit dem aktuellen Parameter der beiden anderen Dimensionen instanziiert werden.
Formaler Typ-Parameter der Oberklasse wird durch aktuellen Parameter ersetzt
Eine andere Möglichkeit besteht darin den formalen Typparameter der Oberklasse durch einen aktuellen zu ersetzen und gleichzeitig einen neuen formalen Typparameter einzuführen. Hier haben die Klassendeklarationen die folgende Form:
public class Unterklasse<T> extends Oberklasse<konkrete-Klasse>
Ein Beispiel hierfür sei eine zweidimensionale Koordinate die über ein generisches Gewichtsattribut verfügt. Als aktueller Typparameter wird hier der Typ Double ausgewählt:
package s2.generics;
/**
*
* @author s@scalingbits.com
* @param <T> Die Dimensionen der Koordinate
*/
public class Koordinate2DGewicht<T> extends Koordinate<Double> {
private T gewicht;public T getGewicht() {return gewicht;}
public void setGewicht(T g) {gewicht = g;}public Koordinate2DGewicht (Double x, Double y, T g) {
super (x,y);
gewicht = g;
}@Override
public String toString() {return super.toString()+", Gewicht: "+ gewicht;}public static void main (String[] args) {
Koordinate2DGewicht<Double> k1 = new Koordinate2DGewicht<Double>(1.1d, 2.2d, 9.9d);
double dx = k1.getX();
System.out.println(k1);Koordinate2DGewicht<Integer> k2 = new Koordinate2DGewicht<Integer>(1.1d,2.2d,9);
System.out.println(k2);
}
}
Achtung: Der formale Typparamter T der Klasses Koordinate2Gewicht wurde an dieser Stelle neu eingeführt. Er ist ein anderer als der formale Typparameter T der Oberklasse Koordinate!
Generische Unterklasse leitet aus nicht generischer Oberklasse ab
Eine generische Klasse auch eine nicht generische Klasse erweitern. Hier wird der Typ-Parmater neu in die Klassenhierarchie eingeführt. Die Klassendeklarationen genügen dann der folgenden Form:
public class Unterklasse<T> extends Oberklasse
Ein Beispiel hierfür ist eine nicht generische Oberklasse Koordinate2D aus der eine generische Unterklasse Koordinate2DGewichtGen<T> abgeleitet wird:
package s2.generics;
/**
*
* @author s@scalingbits.com
*/
public class Koordinate2D {
private Double x;
private Double y;public Double getX() {return x;}
public void setX(Double x) {this.x = x;}
public Double getY() {return y;}
public void setY(Double y) { this.y = y;}public Koordinate2D(Double xp, Double yp ) {
x = xp;
y = yp;
}@Override
public String toString() {return "x: " + x + "; y: " + y;}public static void main (String[] args) {
Koordinate2D k1 = new Koordinate2D(2.2d, 3.3d);
System.out.println(k1);
}
}
public class Koordinate2DGewichtGen<T> extends Koordinate2D {
private T gewicht;
public T getGewicht() {return gewicht;}
public void setGewicht(T g) {gewicht = g;}public Koordinate2DGewichtGen (Double x, Double y, T g) {
super (x,y);
gewicht = g;
}@Override
public String toString() {return super.toString()+", Gewicht: "+ gewicht;}public static void main (String[] args) {
Koordinate2DGewichtGen<Double> k1 = new Koordinate2DGewichtGen<Double>(1.1d, 2.2d, 9.9d);
double dx = k1.getX();
System.out.println(k1);Koordinate2DGewichtGen<Integer> k2 = new Koordinate2DGewichtGen<Integer>(1.1d,2.2d,9);
System.out.println(k2);
}
}
Nicht generische Unterklasse leitet aus generischer Oberklasse ab
Die letzte Variante besteht aus nicht generischen Klassen die aus einer generischen Oberklasse ableiten in dem sie einen aktuellen Typ-Parameter für die Unterklasse aus der Oberklasse wählen. Die Klassendeklarationen dieser Klassen haben die folgende Schreibweise:
public class Unterklasse extends Oberklasse<konkrete-Klasse>
Ein Beispiel hierfür sei die Klasse Koordinate3DDouble die nicht generisch ist und für die x und y Koordinate die Klasse Koordinate mit dem aktuellen Typparameter Double verwendet:
package s2.generics;
/**
*
* @author s@scalingbits.com
*/
public class Koordinate3DDouble extends Koordinate<Double> {
private Double z;public Double getZ() {return z;}
public void setZ(Double z) {this.z = z;}@Override
public String toString() {return super.toString()+", z: "+ z;}public Koordinate3DDouble (Double x, Double y, Double z) {
super (x,y);
this.z = z;
}public static void main (String[] args) {
Koordinate3DDouble k1 = new Koordinate3DDouble(1.1d, 2.2d, 3.3d);
double d1 = k1.getX(); //Generezität nicht mehr sichtbar
System.out.println(k1);
}
}
- 13326 views
allgemeine Sytax-Regeln
Müssten die Syntax-Regeln nicht eigentlich Oberklasse
- Log in to post comments
Wildcards
Wildcards"Wildcards" sind ein Begriff aus dem Englischen und beziehen sich auf die Joker im Pokerspiel, die vielfältig eingesetzt werden können. Der Begriff wird im Computerumfeld verwendet wenn es um Platzhalter für andere Zeichen geht.
Unbound Wildcards
Die Wildcards werden benötigt um mit Referenzen auf generische Objekte zu zeigen und deren Ausprägung mehr oder weniger allgemein zu definieren. Eine Referenz auf ein Objekt der verwendeten generischen Klasse Koordinate<T> kann man mit einer Wildcard so beschreiben:
Koordinate<?> zeiger;
Ein Fragezeichen ? ist eine "unbound Wildcard". Sie erlaubt es auf jeden beliebigen Typ der Klasse Koordinate<T> zu zeigen.
Beispiel:
package s2.generics; /** * * @author s@scalingbits.com */ public class Koordinate<T> { private T x; private T y; public T getX() {return x;} public void setX(T x) {this.x = x;} public T getY() {return y;} public void setY(T y) { this.y = y;} public Koordinate(T xp, T yp ) { x = xp; y = yp; } @Override public String toString() {return "x: " + x + "; y: " + y;} public static void main (String[] args) { Koordinate<Double> k1 = new Koordinate<Double>(2.2d, 3.3d); System.out.println(k1); Koordinate<Integer> k2 = new Koordinate<Integer>(2, 3); System.out.println(k2); Koordinate<Number> k3 = new Koordinate<Number>(2l, 3l); System.out.println(k3); k3 = new Koordinate<Number>(4.4f, 5.5f); System.out.println(k3); Koordinate<?> zeiger; zeiger = k1; zeiger = k2; zeiger = k3; } } // Ende der Klasse Koordinate
Die Referenzvariable zeiger kann im gezeigten Beispiel auf beliebig parametrisierte Objekte der Klasse Koordinate<T> zeigen.
Wichtig |
---|
Die Wildcard kann nicht in der Sektion des Typ-Parameter einer Klasse, Methode oder Schnittstelle stehen! Sie kann nur im Kontext von Referenzvariablen verwendet werden! |
Umgangssprachlich: Die Wildcard findet man immer nur links des Zuweisungsoperators oder in einer Variablendeklaration.
Die Upper Bound Wildcard
Die bisher verwendeten Typparameter erlauben die Instanziierung einer Klasse mit jeder beliebigen Klasse (die aus der Klasse Object abgeleitet wird). Dies ist oft zu allgemein und kann kontraproduktiv sein.
Bei der Klasse Koordinate macht es keinen Sinn sie mit dem Typ-Parameter Boolean zu parametrisieren:
Koordinate<Boolean> k = new Koordinate<Boolean>(true, true);
Das oben gezeigte Beispiel ist eine korrekte Zuweisung die man übersetzen und ausführen kann. Man kann eine solche Verwendung unterbinden wenn die Typenstruktur Klassenhierachie von Java für Zahlen nutzt:
Man kann die Klasse Number als Oberklasse für alle erlaubten Parametrisierungen wählen und den generischen Typ der Klasse Koordinate<T> einschränken:
public class Koordinate<T extends Number>
Durch diese Notation wird der formale Typparameter auf die Klasse Number oder Spezialisierungen daraus beschränkt. Man spricht von einer "Upper Bound Wildcard" weil die Verwendung von Klassen nach oben (zur Wurzel der Klasse) hin beschränkt ist.
Die Lower Bound Wildcard
Neben der "Upper Bound Wildcard" gibt es auch eine "Lower Bound Wild Card". Sieht wird trotz des ähnlichen Namens, sehr unterschiedlich verwendet.
Sie wird ausschließlich mit der "Unbound Wildcard" in der Schreibweise "? super Lowerbound" bei der Typfestlegung von Referenzen verwendet.
Die generische Klasse selbst ist hier beliebig. Sie sei:
public class MyGenericClass<T> { // Inhalt der Klasse ist nicht wichtig }
Weiterhin sei eine Klassenhierarchie mit Klassen von A bis F gegeben:
mit Hilfe der "Lower Bound Wildcard" kann die Verwendung einer Referenz so eingeschränkt werden, dass nur Instanzen der Klasse C oder Klassen von denen C abgeleitet wurde, verwendet werden darf.
public class TestClass { ... public void testCreate(MyGenericClass<? super C> zeiger) { .... } public void test () { TestClass.testCreate(new MyGenericClass<C>()); // Korrekt TestClass.testCreate(new MyGenericClass<A>()); // Korrekt TestClass.testCreate(new MyGenericClass<Object>()); // Korrekt TestClass.testCreate(new MyGenericClass<F>()); // Fehler!! TestClass.testCreate(new MyGenericClass<D>()); // Fehler!! } }
Die Typ-Parameter der Klassen E, F und B können nicht übergeben werden, da sie nicht in der Typhierarchie der Klasse C vorkommen.
Im Java API werden auch "Lower Bound Wildcards" verwendet. Ein Beispiel ist die drainTo() Methode der Schnittstelle BlockingQueue:
int drainTo(Collection<? super E> c, int maxElements)
- 8738 views
Der "Diamondoperator" in Java
Der "Diamondoperator" in JavaBei der Verwendung generischer Datentypen ensteht zuätzlicher Schreibaufwand, da man den Typ inklusive seines generischen Typs an vielen Stellen nennen muss. Ein Bespiel hierfür ist:
Koordinate<Double> meinPunkt = new Koordinate<Double>(1.1D,2.2D);
Man muss den generischen Typ Double in der Deklaration der Referenzvariable, wie auch im Konstruktoraufruf beim Anlegen des Objekts nennen.
Seit Java 7 ist es erlaubt eine verkürzte Schreibweise zu verwenden. Man nennt diese verkürzte Schreibweise den "Diamondoperator" weil das Kleiner- und Größerzeichen an einen Kristall erinnern. Das obige Beispiel lässt sich auch verkürzt so programmieren:
Koordinate<Double> meinPunkt = new Koordinate<>(1.1D,2.2D);
Der Übsetzer leitet sich die notwendige Typinformation beim Konstruktoraufruf aus dem generischen Typ der Referenzvariable ab.
Bemerkung: Dies ist eine stark verkürzte Erklärung.
Die automatische Bestimmung des Typs durch den Übersetzer kann recht kompliziert sein, da die Konzepte der Vererbung, Interfaces (Schnittstellen), Casts und Autoboxing beachtet werden müssen.
Weiterführende Ressourcen
- 3034 views
Übungen, Fragen (Generics)
Übungen, Fragen (Generics)1. Frage: Instanziierungen
Gegeben seien die folgenden Klassen:
public class Kaefig<T> {
private T einTier;
public void setTier(T x) {
einTier = x;
}
public T getTier() {
return einTier;
} }
public class Tier{ }
public class Hund extends Tier { }
public class Vogel extends Tier { }
Beschreiben Sie was mit dem folgenden Code geschieht. Die Möglichkeiten sind
- er übersetzt nicht
- er übersetzt mit Warnungen
- er erzeugt Fehler während der Ausführung
- er übersetzt und läuft ohne Probleme
1.1
Kaefig<Tier> zwinger = new Kaefig<Hund>();
1.2
Kaefig<Vogel> voliere = new Kaefig<Tier>();
1.3
Kaefig<?> voliere = new Kaefig<Vogel>();
voliere.setTier(new Vogel());
1.4
Kaefig voliere = new Kaefig();
voliere.setTier(new Vogel());
2. Umwandeln einer nicht generischen Implementierung in eine generische Implementierung
In diesem Beispiel werden Flaschen mit verschiedenen Getränken gefüllt und entleert. Das Befüllen und Entleeren erfolgt in der main() Methode der Klasse Flasche.
Die vorliegende Implementierung erlaubt das Befüllen der Flaschen f1 und f2 mit beliebigen Getränken.
Aufgabe:
Ändern Sie die Implementierung der Klasse Flasche in eine generische Klasse die für unterschiedliche Getränke parametrisiert werden kann.
- passen Sie alle Methoden der Klasse Flasche so an das sie mit einem parametrisierten Getränk befüllt werden können (Bier oder Wein)
- Ändern Sie die main() Methode derart, dass f1 nur mit Bier befüllt werden kann und f2 nur mit Wein.
2.1 Klasse Flasche
Die Klasse dient als Hauptprogramm. In dieser Übung müssen ausschließlich die Methoden dieser Klasse und das Hauptprogramm angepasst werden.
package s2.generics;
/**
*
* @author s@scalingbits.com
*/
public class Flasche {Getraenk inhalt = null;
public boolean istLeer() {
return (inhalt == null);
}public void fuellen(Getraenk g) {
inhalt = g;
}public Getraenk leeren() {
Getraenk result = inhalt;
inhalt = null;
return result;
}public static void main(String[] varargs) {
// in generischer Implementierung soll
// f1 nur für Bier dienen
Flasche f1 = new Flasche();
f1.fuellen(new Bier("DHBW-Bräu"));
System.out.println("f1 geleert mit " + f1.leeren());
f1 = new Flasche();
f1.fuellen(new Bier("DHBW-Export"));
System.out.println("f1 geleert mit " + f1.leeren());
// In der generischen Implementierung soll f2 nur für
// Weinflaschen dienen
Flasche f2;
f2 = new Flasche();
f2.fuellen(new Weisswein("Pfalz"));
System.out.println("f2 geleert mit " + f2.leeren());
f2 = new Flasche();
f2.fuellen(new Rotwein("Bordeaux"));
System.out.println("f2 geleert mit " + f2.leeren());
}
}
2.2 Klasse Getraenk
package s2.generics;public abstract class Getraenk {
}
2.3 Klasse Bier
package s2.generics;public class Bier extends Getraenk {
private String brauerei;
public Bier(String b) { brauerei = b;}
public String getBrauererei() {
return brauerei;
}
public String toString() {return "Bier von " + brauerei;}
}
2.4 Klasse Wein
package s2.generics;public class Wein extends Getraenk {
private String herkunft;public String getHerkunft() {
return herkunft;
}public String toString(){ return ("Wein aus " + herkunft);}
public Wein (String origin) {
herkunft = origin;
}}
2.5 Klasse Weisswein
package s2.generics;public class Weisswein extends Wein {
public Weisswein(String h) {super(h);}}
2.6 Klasse Rotwein
package s2.generics;public class Rotwein extends Wein {
public Rotwein(String h) {super(h);}}
3. Typprüfungen
Welche Zeilen in der main() Methode werden vom Übersetzer nicht übersetzt?
Markieren Sie die Zeilen und nennen Sie den Fehler:
package s2.generics;
/**
*
* @author s@scalingbits.com
* @param <T> ein Getraenk
*/
public class KoordinateTest<T extends Number> {public T x;
public T y;public KoordinateTest(T xp, T yp) {
x = xp;
y = yp;
}public static void main(String[] args) {
KoordinateTest<Double> k11, k12;
KoordinateTest<Integer> k21, k22;
Koordinate<String> k31;
KoordinateTest<Number> k41, k42;
KoordinateTest<Float> k81, k82;
k81 = new KoordinateTest<Float>(2.2f, 3.3f);
k12 = new KoordinateTest<Double>(2.2d, 3.3d);
k21 = new KoordinateTest<Integer>(2, 3);
//k31 = new Koordinate<String>("11","22");
k41 = new KoordinateTest<Number>(2l, 3l);
k41 = new KoordinateTest<Number>(4.4d, 5.5f);
k11 = new KoordinateTest<Double>(3.3d,9.9d);
KoordinateTest<? super Double> k99;
k99 = k11;
k99 = k41;
k99 = k31;
k11 = k12;
k12 = k21;
KoordinateTest k55 = new KoordinateTest<Number>(7.7f, 8.8f);
KoordinateTest k66 = new KoordinateTest(7.7d, 3.d);
}
}
- 10726 views
Lösungen, Antworten
Lösungen, Antworten1. Antwort: Instanziierungen
Gegeben seien die folgenden Klassen:
public class Kaefig<T> { private T einTier; public void setTier(T x) { einTier = x; } public T getTier() { return einTier; } public class Tier{ } public class Hund extends Tier { } public class Vogel extends Tier { } }
Beschreiben Sie was mit dem folgenden Code geschieht. Die Möglichkeiten sind
- er übersetzt nicht
- er übersetzt mit Warnungen
- er erzeugt Fehler während der Ausführung
- er übersetzt und läuft ohne Probleme
1.1
Kaefig<Tier> zwinger = new Kaefig<Hund>();
Übersetzungsfehler: Hund ist zwar eine Unterklasse von Tier. Kaefig<Tier> und Kaefig<Hund> sind keine kompatiblen Typen.
1.2
Kaefig<Vogel> voliere = new Kaefig<Tier>();
Übersetzungsfehler: Vogel ist zwar eine Unterklasse von Tier. Kaefig<Tier> und Kaefig<Vogel> sind keine kompatiblen Typen.
1.3
Kaefig<?> voliere = new Kaefig<Vogel>(); voliere.setTier(new Vogel());
Übersetzungsfehler in der zweiten Zeile. Die erste Zeile ist in Ordnung. Man kann einen Kaefig eines unbekannten aktuellen Parametertyps erzeugen. Die zweite Zeile kann nicht übersetzen, da der Übersetzer nicht wissen kann welche Tiere in voliere verwaltet werden sollen. Der folgende Code würde übersetzen:
Kaefig<?> voliere = new Kaefig<Vogel>(); Kaefig<Vogel> k= new Kaefig<Vogel>(); k.setTier(new Vogel()); voliere=k;
Anmerkung: Man darf nur Referenzen auf Zeiger mit Wildcards (hier voliere) zuweisen. Man darf keine Objektmethoden aufrufen weil hierfür der Typ zur Übersetzungszeit nicht bekannt ist.
1.4
Kaefig voliere = new Kaefig(); voliere.setTier(new Vogel());
Der Übersetzer übersetzt den Quellcode mit einer Warnung. Er kann nicht wissen welchen Typ er benutzt. Er erzeugt deshalb eine Warnung, weil es beim Zuweisen eines Vogels zu Problemen kommen kann. Man verliert die Vorteile der Typprüfung der generischen Klassen. Dieser Codierstil sollte deshalb vermieden werden.
2. Umwandeln einer nicht generischen Implementierung in eine generische Implementierung
package s2.generics; /** * * @author s@scalingbits.com * @param <T> ein Getraenk */ public class FlascheGeneric<T extends Getraenk> { T inhalt = null; public boolean istLeer() { return (inhalt == null); } public void fuellen(T g) { inhalt = g; } public T leeren() { T result = inhalt; inhalt = null; return result; } public static void main(String[] varargs) { // in generischer Implementierung soll // f1 nur für Bier dienen FlascheGeneric<Bier> f1 = new FlascheGeneric<Bier>(); f1.fuellen(new Bier("DHBW-Bräu")); System.out.println("f1 geleert mit " + f1.leeren()); f1 = new FlascheGeneric<Bier>(); f1.fuellen(new Bier("DHBW-Export")); System.out.println("f1 geleert mit " + f1.leeren()); // In der generischen Implementierung soll f2 nur für // Weinflaschen dienen FlascheGeneric<Wein> f2; f2 = new FlascheGeneric<Wein>(); f2.fuellen(new Weisswein("Pfalz")); System.out.println("f2 geleert mit " + f2.leeren()); f2 = new FlascheGeneric<Wein>(); f2.fuellen(new Rotwein("Bordeaux")); System.out.println("f2 geleert mit " + f2.leeren()); } }
3. Typprüfungen
Welche Zeilen in der main() Methoder werden vom Übersetzer nicht übersetzt?
Markieren Sie die Zeilen und nennen Sie den Fehler:
package s2.generics; public class KoordinateTest<T extends Number> { public T x; public T y; public KoordinateTest(T xp, T yp) { x = xp; y = yp; } public static void main(String[] args) { KoordinateTest<Double> k11, k12; KoordinateTest<Integer> k21, k22; //Koordinate<String> k31; Die Klasse String ist nicht aus Number abgeleitet. Siehe extends Klausel KoordinateTest<Number> k41, k42; //k11 = new KoordinateTest<Float>(2.2d, 3.3d); Die Eingabeparameter sind vom Typ double und nicht vom Typ Float k12 = new KoordinateTest<Double>(2.2d, 3.3d); k21 = new KoordinateTest<Integer>(2, 3); //k31 = new Koordinate<String>("11","22"); Nicht erlaubt, da der Typ weiter oben nicht für die Variable erlaubt war k41 = new KoordinateTest<Number>(2l, 3l); k41 = new KoordinateTest<Number>(4.4d, 5.5f); //k11 = new Koordinate<Double>(3.3f,9.9d); Der erste Parameter ist ein Float und nicht ein Double wie gefordert KoordinateTest<? super Double> k99; //k99 = k11; Nicht erlaubt, da der Typ weiter oben nicht für die Variable erlaubt war k99 = k41; //k99 = k31; Nicht erlaubt, da der Typ weiter oben nicht für die Variable erlaubt war k11 = k12; //k12 = k21; k21 ist vom Typ KoordinateTest<Integer>. k12 muss aber vom Typ KoordinateTest<Double> sein KoordinateTest k55 = new KoordinateTest<Number>(7.7f, 8.8f); KoordinateTest k66 = new KoordinateTest(7.7f, 8.8f); } }
- 7144 views
Beispiel: Von einer nicht generischen Klasse zu einer generischen Klasse
Beispiel: Von einer nicht generischen Klasse zu einer generischen KlasseDie Klassen MerkerX implementieren eine Warteschlange der Länge 2. Sie ist in der Lage sich den letzten und den vorletzten Wert zu merken.
Im Folgenden erhalten die Klassen neue Namen (Merker2, Merker3 etc.). Dies erlaubt die Klassen gleichzeitig im Paket zu verwalten
Von Basistypen zu Objekten
Die Klasse Merker1 ist in der Lage Basistypen vom Typ int zu verwalten. Die Werte werden als Teil der Instanzen der Klasse Merker1 verwaltet.
Die Klasse Merker2 verwaltet Objekte vom Typ Integer. Verschiedene Instanzen von Merker2 könne also auf die gleichen Instanzen der Klasse Integer zeigen.
Klasse Merker1 | Klasse Merker2 |
---|---|
package Kurs2.Generics; |
package Kurs2.Generics; |
Von Integer-Objekten zur Verwaltung beliebiger Objekte
Die Klasse Merker3 kann nun nicht nur Instanzen der Klasse Integer verwalten. Sie kann beliebige Objekte verwalten.
Klasse Merker2 | Klasse Merker3 |
---|---|
package Kurs2.Generics; |
package Kurs2.Generics; |
Von der unsicheren Verwaltung beliebiger Objekte zu einer generischen Klasse
Die Klasse Merker4 ist jetzt generisch. Sie kann typsicher Objekte verwalten. In der main() Methode der Klassse Merker4 wird jedoch diese neue gewonnen Fähigkeit ignoriert. Die Klasse Merker4 wird benutzt wie eine Klasse die beliebige Typen verwalten kann.
Klasse Merker3 | Klasse Merker4 |
---|---|
package Kurs2.Generics; public class Merker3 { |
package Kurs2.Generics; public class Merker4<T> { |
Typsichere Nutzung der generischen Klasse
Die Klasse Merker5 ist mit Ausnahme der main() Methode identisch zur Klasse Merker4. In der main() Methode der Klassse Merker4 werden jetzt Objekte f1, f2, f3 angelegt die nur die Verwaltung von bestimmten Typen in der Klasse Merker5 erlauben. Da die aktuellen Typen von f1, f2, f3 unterschiedlich sind kann man Sie nicht mehr beliebig aufeinander zuweisen.
Klasse Merker4 | Klasse Merker5 |
---|---|
package Kurs2.Generics; public class Merker4<T> { |
package Kurs2.Generics; |
Die Klassenhierarchie der verschiedenen Varianten der generischen Klasse Merker5:
Einschränkung der generischen Klasse Merker6 auf bestimmte Typen
Die Klasse Merker6 verwendet das Schlüsselwort extends um die Verwendung auf zahlenartige Typen (Number) einzuschränken. Die Einschränkungen bei den Zuweisungen zwischen den Variablen f1, f2, und f3 sind zu beachten.
Anbei die Klassen Hierarchie des Java API zur Klasse Number und ihrer Unterklassen:
Klasse Merker4 | Klasse Merker5 |
---|---|
package Kurs2.Generics; |
package Kurs2.Generics; public class Merker6<T extends Number> { private T letzter; private T vorletzter; |
Wild Cards
Die Klasse Merker7 benutzt in der main() Methode eine Variable merkeAlles die mit einer Wildcard versehen ist.
Die Variable kann auf alle Varianten der Klasse Merker7 zeigen, sie kann aber nicht alle Methoden nutzen da die Methoden zur Übersetzungszeit nicht bekannt sein müssen.
package Kurs2.Generics;
public class Merker7<T extends Number> {private T letzter;
private T vorletzter;public Merker7() {
letzter = null;
vorletzter = null;
}public T getLetzter() {
return letzter;
}public T getVorLetzter() {
return vorletzter;
}public void merke(T wert) {
vorletzter = letzter;
letzter = wert;
}public String toString() {
return "[" + letzter + ";" + vorletzter + "]";
}public static void main(String[] args) {
// Teil 1: Integer Verwalten
Merker7<Integer> f1 = new Merker7<Integer>();
Integer i10 = 10; // Erzeugung mit Autoboxing
f1.merke(i10);
Integer i11 = new Integer(11); // Reguläre Erzeugung
f1.merke(i11);
Integer i12 = new Integer(12); // Reguläre Erzeugung
f1.merke(i12);
System.out.println(f1);
// Teil 2: Float verwalten
Merker7<Float> f2 = new Merker7<Float>();
Float i20 = 100.1f;
f2.merke(i20);
Float i21 = 101.2f;
f2.merke(i21);
Float i22 = 102.3f;
f2.merke(i22);
System.out.println(f2);
// Teil 3: Alles Verwalten
Merker7<Number> f3 = new Merker7<Number>();
Integer i30 = 200;
f3.merke(i30);
Float i31 = 201.f;
f3.merke(i31);
String i32 = "Zeichenkette";
//f3.merke(i32); // Warum ist dies nicht erlaubt?
System.out.println(f3);
// Teil 4: Alles Verwalten
Merker7 f4 = new Merker7();
Integer i40 = 400;
f4.merke(i40);
Float i41 = 401.f;
f4.merke(i41);
String i42 = "Zeichenkette";
//f4.merke(i42); // Warum ist dies nicht erlaubt?
System.out.println(f4);
f2 = f4;
Merker7<?> merkeAlles;
merkeAlles = f1;
// Hier wird implizit die Methode toString() aufgerufen
System.out.println(merkeAlles);
int m;
// Warum ist diese Zuweisung nicht erlaubt?
//m = merkeAlles.letzter;
// Warum ist diese Zuweisung erlaubt?
m = 3 * f1.letzter;
System.out.println(merkeAlles.letzter);
merkeAlles = f2;
System.out.println(merkeAlles);
merkeAlles = f3;
System.out.println(merkeAlles);
}
}
- 4934 views
Lernziele (Generics)
Lernziele (Generics)
Am Ende dieses Blocks können Sie:
|
Lernzielkontrolle
Sie sind in der Lage die folgenden Fragen zu beantworten: Übungsfragen zu generischen Typen
- 3442 views