Generische Klassen (Generics)

Generische Klassen (Generics)

Duke mit Templates

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.

Definition
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:

Syntaxregel
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:

Syntaxregel
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

 

Stefan Schneider Thu, 04/14/2011 - 16:52

Generics zur Übersetzungs- und Laufzeit

Generics zur Übersetzungs- und Laufzeit

Der 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).

Wichtig

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!

 

Stefan Schneider Fri, 04/15/2011 - 11:09

Generics, Autoboxing, Subtyping

Generics, Autoboxing, Subtyping

Die 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.

 

Stefan Schneider Fri, 04/15/2011 - 16:20

Vererbung und generische Klassen

Vererbung und generische Klassen

Die 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
  • "Klassische" Vererbung

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);
}
}

 

 

 

 

Stefan Schneider Sat, 04/16/2011 - 15:24

Anonymous (not verified)

Tue, 06/14/2016 - 12:08

Müssten die Syntax-Regeln nicht eigentlich Oberklasse heißen, wenn ein konkreter Typ festgelegt wird um die generische, bzw nicht generische Unterklasse abzuleiten?

Ich verstehe Ihre Frage nicht so richtig.
Würden Sie vielleicht einen Vorschlag posten?

Danke.

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;
}

}

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)

 

Stefan Schneider Sun, 04/17/2011 - 11:33

Der "Diamondoperator" in Java

Der "Diamondoperator" in Java

Bei 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

 

Stefan Schneider Sun, 03/13/2016 - 12:17

Ü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);
}
}

 

 

Stefan Schneider Tue, 04/19/2011 - 16:53

Lösungen, Antworten

Lösungen, Antworten

1. 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);
   }
}
Stefan Schneider Tue, 04/19/2011 - 17:21

Beispiel: Von einer nicht generischen Klasse zu einer generischen Klasse

Beispiel: Von einer nicht generischen Klasse zu einer generischen Klasse

 Die 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;

public class Merker1 {
private int letzter;
private int vorletzter;

public Merker1() {
letzter = 0;
vorletzter = 0;
}

public int getLetzter() {
return letzter;
}

public int getVorLetzter() {
return vorletzter;
}

public void merke(int wert) {
vorletzter = letzter;
letzter = wert;
}

public String toString() {
return "[" + letzter + ";" + vorletzter + "]";
}

public static void main(String[] args) {
// Teil 1: int Verwalten
Merker1 f1 = new Merker1();
int i10 = 10;
f1.merke(i10);
int i11 = 11; // Reguläre Erzeugung
f1.merke(i11);
Integer i12 = new Integer(12);//Erz. mit Autoboxing
f1.merke(i12);
System.out.println(f1);

// Teil 2: int verwalten
Merker1 f2 = new Merker1();
int i20 = 100;
f2.merke(i20);
int i21 = 101;
f2.merke(i21);
int i22 = 102;
f2.merke(i22);
System.out.println(f2);

// Teil 3: int verwalten
Merker1 f3 = new Merker1();
int i30 = 200;
f3.merke(i30);
int i31 = 201;
f3.merke(i31);
int i32 = 202;
f3.merke(i32);
System.out.println(f3);

// Warum sind diese Zuweisungen erlaubt ?
f1 = f2;
f2 = f3;
f1 = f3;
}
}

package Kurs2.Generics;

public class Merker2 {
private Integer letzter;
private Integer vorletzter;

public Merker2() {
letzter = null;
vorletzter = null;
}

public Integer getLetzter() {
return letzter;
}

public Integer getVorLetzter() {
return vorletzter;
}

public void merke(Integer wert) {
vorletzter = letzter;
letzter = wert;
}

public String toString() {
return "[" + letzter + ";" + vorletzter + "]";
}

public static void main(String[] args) {
// Teil 1: Integer Verwalten
Merker2 f1 = new Merker2();
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: Integer verwalten
Merker2 f2 = new Merker2();
int i20 = 100;
f2.merke(i20);
int i21 = 101;
f2.merke(i21);
int i22 = 102;
f2.merke(i22);
System.out.println(f2);

// Teil 3: Integer verwalten
Merker2 f3 = new Merker2();
int i30 = 200;
f3.merke(i30);
int i31 = 201;
f3.merke(i31);
int i32 = 202;
f3.merke(i32);
System.out.println(f3);

// Warum sind diese Zuweisungen erlaubt ?
f1 = f2;
f2 = f3;
f1 = f3;
}
}

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;

public class Merker2 {
private Integer letzter;
private Integer vorletzter;

public Merker2() {
letzter = null;
vorletzter = null;
}

public Integer getLetzter() {
return letzter;
}

public Integer getVorLetzter() {
return vorletzter;
}

public void merke(Integer wert) {
vorletzter = letzter;
letzter = wert;
}

public String toString() {
return "[" + letzter + ";" + vorletzter + "]";
}

public static void main(String[] args) {
// Teil 1: Integer Verwalten
Merker2 f1 = new Merker2();
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: Integer verwalten
Merker2 f2 = new Merker2();
int i20 = 100;
f2.merke(i20);
int i21 = 101;
f2.merke(i21);
int i22 = 102;
f2.merke(i22);
System.out.println(f2);

// Teil 3: Integer verwalten
Merker2 f3 = new Merker2();
int i30 = 200;
f3.merke(i30);
int i31 = 201;
f3.merke(i31);
int i32 = 202;
f3.merke(i32);
System.out.println(f3);

// Warum sind diese Zuweisungen erlaubt ?
f1 = f2;
f2 = f3;
f1 = f3;
}
}

package Kurs2.Generics;

public class Merker3 {
private Object letzter;
private Object vorletzter;

public Merker3 () {
letzter = null;
vorletzter = null;
}

public Object getLetzter() {
return letzter;
}

public Object getVorLetzter() {
return vorletzter;
}

public void merke(Object wert) {
vorletzter = letzter;
letzter = wert;
}

public String toString() {
return "["+ letzter +";" + vorletzter + "]";
}

public static void main (String[] args) {
// Teil 1: Integer Verwalten
Merker3 f1 = new Merker3();
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
Merker3 f2 = new Merker3();
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
Merker3 f3 = new Merker3();
Integer i30 = 200;
f3.merke(i30);
Float i31 = 201.f;
f3.merke(i31);
String i32 = "Zeichenkette";
f3.merke(i32);
System.out.println (f3);

// Warum sind diese Zuweisungen erlaubt ?
f1 = f2;
f2 = f3;
f1 = f3;
}
}

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 {
private Object letzter;
private Object vorletzter;

public Merker3 () {
letzter = null;
vorletzter = null;
}

public Object getLetzter() {
return letzter;
}

public Object getVorLetzter() {
return vorletzter;
}

public void merke(Object wert) {
vorletzter = letzter;
letzter = wert;
}

public String toString() {
return "["+ letzter +";" + vorletzter + "]";
}

public static void main (String[] args) {
// Teil 1: Integer Verwalten
Merker3 f1 = new Merker3();
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
Merker3 f2 = new Merker3();
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
Merker3 f3 = new Merker3();
Integer i30 = 200;
f3.merke(i30);
Float i31 = 201.f;
f3.merke(i31);
String i32 = "Zeichenkette";
f3.merke(i32);
System.out.println (f3);

// Warum sind diese Zuweisungen erlaubt ?
f1 = f2;
f2 = f3;
f1 = f3;
}
}

package Kurs2.Generics;
public class Merker4<T> {
private T letzter;
private T vorletzter;

public Merker4 () {
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
Merker4 f1 = new Merker4();
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
Merker4 f2 = new Merker4();
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
Merker4 f3 = new Merker4();
Integer i30 = 200;
f3.merke(i30);
Float i31 = 201.f;
f3.merke(i31);
String i32 = "Zeichenkette";
f3.merke(i32);
System.out.println (f3);

// Warum sind diese Zuweisungen erlaubt ?
f1 = f2;
f2 = f3;
f1 = f3;

}
}

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> {
private T letzter;
private T vorletzter;

public Merker4 () {
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
Merker4 f1 = new Merker4();
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
Merker4 f2 = new Merker4();
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
Merker4 f3 = new Merker4();
Integer i30 = 200;
f3.merke(i30);
Float i31 = 201.f;
f3.merke(i31);
String i32 = "Zeichenkette";
f3.merke(i32);
System.out.println (f3);

// Warum sind diese Zuweisungen erlaubt ?
f1 = f2;
f2 = f3;
f1 = f3;
}
}

package Kurs2.Generics;
public class Merker5 <T> {
private T letzter;
private T vorletzter;

public Merker5 () {
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
Merker5<Integer> f1 = new Merker5<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
Merker5<Float> f2 = new Merker5<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
Merker5<Integer> f3 = new Merker5<Integer>();
Integer i30 = 200;
f3.merke(i30);
Float i31 = 201.f;
//f3.merke(i31);
String i32 = "Zeichenkette";
//f3.merke(i32);
System.out.println (f3);

// Welche Zuweisungen sind erlaubt ?
//f1 = f2;
//f2 = f3;
//f1 = f3;

}
}

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;
public class Merker5 <T> {
private T letzter;
private T vorletzter;

public Merker5 () {
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
Merker5<Integer> f1 = new Merker5<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
Merker5<Float> f2 = new Merker5<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
Merker5<Integer> f3 = new Merker5<Integer>();
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);

// Welche Zuweisungen sind erlaubt ?
//f1 = f2;
//f2 = f3;
//f1 = f3;

}
}

package Kurs2.Generics;
public class Merker6<T extends Number> {
    private T letzter;
    private T vorletzter;

public Merker6() {
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
Merker6<Integer> f1 = new Merker6<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
Merker6<Float> f2 = new Merker6<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 Verwalte
Merker6<Number> f3 = new Merker6<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);

// f1 = f2; // Warum ist dies nicht erlaubt?
// f2 = f3; // Warum ist dies nicht erlaubt?
//
}
}

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);
}
}

Stefan Schneider Sat, 05/14/2011 - 10:28

Lernziele (Generics)

Lernziele (Generics)

Am Ende dieses Blocks können Sie:

  • ... die Syntax zur Deklaration generischer Typen anwenden.
  • ... die erhöhte Typsicherheit die durch die generischen Javatypen eingeführt werden erklären.
  • ... erklären warum generische Datentypen eine Form des Polymorphismus sind.
  • ... den Zusammenhang zwischen Subtypen (Vererbung), Autoboxing und generischen Typen erklären und bei Zuweisungen die Typsicherheit erkennen.
  • ... können die verschiedenen Kombinationen von generischen Typen und Spezialisierung (bei der Vererbung) anwenden.
  • ... mit "bound wildcards" und "unbound wildcards" umgehen und nicht Probleme mit Typkonversionen bei Zuweisungen erkennen und lösen.

Lernzielkontrolle

Sie sind in der Lage die folgenden Fragen zu beantworten: Übungsfragen zu generischen Typen

Zur Umfrage

QR Code für Umfrage

Stefan Schneider Tue, 01/01/2013 - 14:04