Literatur [6-1] Nowak, Johannes: Fortgeschrittene Programmierung mit Java 5. dpunkt, 2005 [6-2] Mössenböck, Hanspeter: Sprechen Sie Java? dpunkt, 3. Auflage, 2005 [6-3] Fischer, Gregor; von Gudenberg, Jürgen W.: Programmieren in Java 1.5. Springer, 2005 [6-4] Grude, Ulrich: Java ist eine Sprache. Vieweg, 2005 Komponenten – WS 2014/15 – Teil 5/Generics 2 Idee der parametrisierten Datentypen I • Parameter sind eigentlich Werte entsprechend einem deklarierten Typ. Dies ist der Normalfall in Java. • Was ist aber nun, wenn ein allgemeiner Typ mehrere Untertypen besitzen kann, die zusammen jeweils Varianten bilden? • In Java ist dies bis Java 1.4 nur möglich, wenn die Varianten einen gemeinsamen Vorfahren innerhalb der Vererbungshierarchie haben. Die Oberklasse ist der allgemeine Typ, während alle davon abgeleiteten Unterklassen Varianten sind. • Wenn nun ein Parameter einen Typ als Wert hat und dieser Parameter bei der Erzeugung benutzt wird, um eine "Spezialisierung" zu realisieren, wird von einem parametrisierten Datentyp gesprochen. • Dies geht erst ab Java-Version 1.5. In Java wird so ein Typ generisch genannt (Generics). 3 Komponenten – WS 2014/15 – Teil 5/Generics Idee der parametrisierten Datentypen II Ein Beispiel: (Freistil) Class Queue(ElemType) { ElemType content[10]; int in:=0, out:= 0; … … … proc enqueue(ElemType elem) { … … … } func ElemType dequeue() { … … … } } Queue(int) intqueue; Queue(String) strqueue; intqueue.enqueue(10); strqueue.enqueue(„Elvira“); Komponenten – WS 2014/15 – Teil 5/Generics 4 Die Ziele • Spezialisierung von Typen durch unterschiedlich gesetzte Werte (Typen) bei der Objektgenerierung. • Trotzdem sollte der Compiler die Typverträglichkeiten prüfen können, d.h. kein cast. • Da Generics später in Java integriert werden, sollte eine Kompatibilität zur alten Sprache erhalten bleiben. • Um den Aufwand zu reduzieren, sollten parametrisierte Datentypen durch den Compiler in "normale" Datentypen übersetzt werden, so dass die Virtuelle Maschine davon nicht betroffen wird. Vorteile davon: – Alte Programme laufen binär weiter – Keine neue Virtuelle Maschine Es wird sich herausstellen, dass die letzten beiden Ziele zu einem komplexen und schlechten Sprachentwurf führen. Komponenten – WS 2014/15 – Teil 5/Generics 5 Bisher: Liste von Objekten I class Queue { Object[] daten; ... public void enqueue(Object elem) {...} public Object dequeue() {...} ... } • Um eine Warteschlange für beliebige Objekte zu implementieren, müssen die Elemente vom Typ Object sein, da per definitionem alle Objekte von diesem Typ abgeleitet und daher verträglich sind. • Diese Art von Klasse wird nun auch Rohtyp bzw. raw type genannt. Komponenten – WS 2014/15 – Teil 5/Generics 6 Bisher: Liste von Objekten II Queue names= new Queue(); names.enqueue("Haribald"); names.enqueue(42); ... String n= (String)names.dequeue(); String m= (String)names.dequeue(); // Laufzeitfehler • Solange verabredungsgemäß in die Liste nur Objekte vom "richtigen" Datentyp - hier String - abgelegt werden, geht alles mit dem cast gut. • Auch muss klar sein, dass trotz richtigem Inhalt auch der richtige cast-Typ verwendet wird. • Es wäre schön, wenn dies der Compiler prüfen könnte, und wenn die Verwendung der Casts nicht erforderlich wäre. Komponenten – WS 2014/15 – Teil 5/Generics 7 Wunsch: Liste von Objekten III Queue names= new Queue(String); names.enqueue("Haribald"); names.enqueue(42); // Compiler: Fehler! ... String n= names.dequeue(); String m= names.dequeue(); • Das ist die Idee von Typ-Parametern. • Kein cast und trotzdem nur der richtige Typ • Aber: So ist es in Java nicht realisiert. Komponenten – WS 2014/15 – Teil 5/Generics 8 Java: Liste von Objekten IV class Queue<T> { T[] daten; ... public void enqueue(T elem) {...} public T dequeue() {...} ... } • Für Klassen werden zusätzliche Parameter in spitzen Klammern hinter dem Namen eingefügt, die innerhalb der Klasse dort stehen dürfen, wo Typen stehen dürfen. • Als Typen für T kommen nur Referenztypen in Frage, also Klassen und Interfaces - verkürzt wird von nun an von Klassen gesprochen. • Primitive Typen wie int oder float sind nicht als Werte erlaubt. • Verabredungsgemäß werden Typ-Parameter mit einem Buchstaben benannt – möglich wären aber auch Identifier mit mehreren Buchstaben. Komponenten – WS 2014/15 – Teil 5/Generics 9 Queue I - Ausgeprägter Typ Queue<String> names= new Queue<String>(); names.enqueue("Haribald"); names.enqueue(42); // Compiler: Fehler! ... String n= names.dequeue(); String m= names.dequeue(); • Queue<String> kann als "Queue of String" ausgesprochen werden. • Leider(?) hat die Parameterversorgung von Klassen NICHT die Semantik von Makros, da weitere Regeln existieren, die eine Makroauffassung nicht erlauben. Komponenten – WS 2014/15 – Teil 5/Generics 10 Begriffe • Konkreter Typ = Ausgeprägter Typ = Generische Klasse mit angegebenen Typen • Generische Klasse = Klasse mit formalen Typparametern • Bei der Klassendefinition sollte mit kurzen Namen in Großbuchstaben gearbeitet werden - was nicht gerade schön ist, wird aber so empfohlen. • Es sind auch Listen von Parametern möglich: class HashTable<K, V> { .... oder class HashTable<KeyType, ValueType> { .... 11 Komponenten – WS 2014/15 – Teil 5/Generics Queue II - Rohtyp Compiler class Queue<T> { class Queue { T[] daten; Object[] daten; ... ... public void enqueue(T elem) { } public void enqueue(Object elem) { } public T dequeue() { } public Object dequeue() { } ... ... } } Programmiert Laufzeit Der Compiler übersetzt nun alle Varianten mit gesetztem Typparameter zu einer einzigen (Code-)Instanz mit dem Typwert Object. Zur Laufzeit sind daher die Typinformationen des Parameter nicht vorhanden. Komponenten – WS 2014/15 – Teil 5/Generics 12 Verwendungsregeln I Die Regeln der Verwendung (Lesen, Zuweisen, cast) sind nun so gewählt, dass der Compiler die richtige Verwendung prüfen kann. Alles, was nur zur Laufzeit geprüft werden kann, ist verboten. Queue rq= new Queue<String>; Queue<Integer> iq= new Queue<Integer>; Queue<String> sq= iq; // verboten Warum? iq.enqueue(17); String val= sq.dequeue(); // autoboxing // Integer! • • // erlaubt Rohtypen sind zuweisungskompatibel - aber: Das Mischen von Rohtypen mit generischen Klassen gibt nur Probleme. Unterschiedlich ausgeprägte Typen sind inkompatibel. 13 Komponenten – WS 2014/15 – Teil 5/Generics Verwendungsregeln II (1)class Queue (2)class Queue<Object> { ... { ... • Dies sind unterschiedliche Klassen, die in beiden Richtungen nicht kompatibel sind. • Dies gilt, obwohl alle Typparameter bei der internen Generierung des Rohtyps in Object gewandelt werden. • Oder: Das Konzept der <T> einfach als reinen Makromechanismus ohne weitere Regeln aufzufassen, ist falsch (obwohl dies in vielen Fällen sinnvoll wäre). Komponenten – WS 2014/15 – Teil 5/Generics 14 Keine Felder von Typparametern I class Queue<T> { T[] daten; T[] daten= new T[10]; ... // erlaubt // verboten! • Felder von Typparametern sind nicht erlaubt. • Der Grund liegt in der Implementierungstechnik: – der Compiler konvertiert intern den generischen Typ in den Rohtyp, d.h. alle Parameter werden nach Object konvertiert. – Wenn aus diesem Array gelesen wird, ist der tatsächlich verwendete Typ im Array unbekannt. 15 Komponenten – WS 2014/15 – Teil 5/Generics Keine Felder von Typparametern II class Queue<T> { T[] daten; T[] daten= new T[10]; Object[] oArray= daten; oArray[0]= "blabla"; .... } // // // // // erlaubt Annahme: kein Fehler erlaubt nur dann in Ordnung, wenn T ein String ist. • Da die Typtests zur Compile-Zeit ablaufen und für diesen Fall dies zur Laufzeit erforderlich ist (Denken Sie daran, dass Arrays auch Methodenresultate sein können), wird dies einfach verboten. • Beachten Sie auch die interne Konvertierung in einen Rohtyp. Komponenten – WS 2014/15 – Teil 5/Generics 16 Keine Felder von Typparametern III class Queue<T> { T[] daten; T[] daten= new T[10]; Object[] oArray= daten; oArray[0]= "blabla"; oArray[1]= Integer(34); ... } // // // // // in Ordnung Annahme: kein Fehler in Ordnung Hmmm... Und jetzt? • Ein wichtiger Grund für das Verbot von generischen Arrays liegt darin, dass nicht mehr sichergestellt werden kann, dass immer alle Elemente vom selben kompatiblen Typ sind. • Und wie sieht es hier mit der Typsicherheit aus? • Das Erzeugen mit new() sei es für ein Objekt oder für ein Array mit einem Parameter als Typ ist daher nicht erlaubt. Komponenten – WS 2014/15 – Teil 5/Generics 17 Lösung für Arrays I class Queue<T> { T[] daten; // in Ordnung T[] daten= (T[])new Object[10]; // Warnung durch Compiler ... } Queue<String>[] qArray= new Queue<String>[10]; Queue<String>[] qArray= (Queue<String>[])new Queue<String>[10]; • Die erste Lösung ist das cast - was den Offenbarungseid des gesamten Ansatzes darstellt. • Der Compiler meldet eine Warnung, da hier etwas nicht geprüft werden kann, d.h. es kommt auf die Disziplin der Programmierers wieder an. • Bei der Ausprägung sind auch wieder Arrays möglich. Komponenten – WS 2014/15 – Teil 5/Generics 18 Lösung für Arrays II class Container { Object[] daten= new Object[10]; ... public void set(int index, String val) {...} public String get(int index) { return (String)daten[index]; } } • Es wird eine eigene (normale) Klasse mit expliziter Typprüfung und Typcasts für das Array geschrieben. Dadurch wird Typsicherheit gewahrt. • Statt daten wird dann diese Container-Klasse verwendet, sofern es sich bei dem formalen Typparameterwert um String handelt. • Nachteil: Für jeden Typ (als Parameterwert muss eine solche Klasse geschrieben werden). Das wäre der Macro-Ansatz, wie er teilweise in C++ benutzt wird. Komponenten – WS 2014/15 – Teil 5/Generics 19 Lösung für Arrays III public class Container<T> { Object[] daten= new Object[10]; public void set(int index, T val) { daten[0]= val; } public T get(int index) { return (T)daten[index]; } } • Warum nicht hierfür eine generische Klasse verwenden? • In Wirklichkeit ist das auch keine Lösung, aber die wohl beste: – Nachteil: Es wird wie früher mit cast gearbeitet. – Vorteil: Diese casts sind auf engem Raum geklammert. Komponenten – WS 2014/15 – Teil 5/Generics 20 Also zusammengefasst... public class Queue<T> { T datum= ( T ) new Object(); T[] feld= (T[]) new Object[10]; } So geht das mit den Objekten bzw. Arrays. 21 Komponenten – WS 2014/15 – Teil 5/Generics Vererbung I class Ober { void meth() {...} } class Unter1 extends Ober { void meth() {...} } class Unter2 extends Ober { void meth() {...} } • Klasse Unter1 ist ein Untertyp von Klasse Ober. • Gilt das auch für Queue<Unter1> und Queue<Ober>? NEIN. Komponenten – WS 2014/15 – Teil 5/Generics 22 Vererbung II (1) (2) (3) (4) Queue<Unter1> un= new Queue<Unter1>(); Queue<Ober> ob= un; // Annahme: wäre erlaubt Ober obj= new Unter2(); // ist erlaubt ob.enqueue(obj); // müsste auch ok sein un.dequeue() liefert später ein Objekt vom Typ Unter2! Die Ursache ist die Zuweisung (2) oder die Parameterübergabe in Zeile (4). (1) (2) (3) (4) Queue<Unter1> un= new Queue<Unter1>(); Queue<Ober> ob= un; // Annahme: wäre erlaubt Ober obj= new Ober(); // ist erlaubt ob.enqueue(obj); // müsste auch ok sein un.dequeue() liefert später ein Objekt vom Typ Ober statt Unter1! Komponenten – WS 2014/15 – Teil 5/Generics 23 Vererbung III • Das Problem etwas abstrakter: Wenn ein Objekt einer Referenzvariablen, deren Typ in der Vererbungshierarchie höher (also Richtung Wurzel) liegt, zugewiesen wird, entsteht ein Informationsverlust über den Typ des Objektes. Dieser Verlust wird zur Laufzeit mittels eines Deskriptors kompensiert. • Durch die Zuweisung in Zeile (2) entsteht eigentlich eine Vereinigung zweier Typen: Ober und Unter1. Objekte beider Typen könnten dann in der Queue bzw. in der ausgeprägten Klasse sein. Daher wird diese Zuweisung verboten. • Dasselbe Problem tritt auch bei dem Problem mit den Arrays auf: hier wurde nach der Zuweisung an eine Object-Variable der Informationsverlust erzeugt. Komponenten – WS 2014/15 – Teil 5/Generics 24 Regeln und Hinweise • Unter/Oberklassen-Verhältnisse übertragen sich nicht auf die korrespondierenden ausgeprägten Klassen. • Parametrisierung und Vererbung beißen sich etwas - liegt eher an der Vererbung bzw. Polymorphie. • Mischungen von Rohtypen und generischen Typen bergen viele Typprobleme. Komponenten – WS 2014/15 – Teil 5/Generics 25 Einschränkungen I interface Comparable<T> { int compareTo(T other); } class SortedQueue<T extends Comparable<T>> { ... public void enqueue(Object elem) {... ... elem.compareTo(daten[i]) ... } ... } • Wenn eine beschränkende Bedingung an die möglichen Typparameter erforderlich ist, hier dass der Typ die Schnittstelle Comparable realisiert, weil eine Vergleichsoperation benötigt wird, wird diese Bedingung durch "extends ..." ausgedrückt. Komponenten – WS 2014/15 – Teil 5/Generics 26 Einschränkungen II (1)... Klassenname<T extends Interfacename[<T>]> { ... (2)... Klassenname<T extends Klassenname [<T>]> { ... • Gleiche Buchstaben in dem <>-Teil bedeuten auch, dass immer gleiche Werte gemeint sind. Soll der Typ T vor dem extends eingeschränkt werden, muss auch T in der Einschränkung vorkommen. • "extends" bedeutet bei Schnittstellen: Der übergebene Typ T muss die Schnittstelle realisieren, d.h. die Methoden der Schnittstelle bereit stellen. • "extends" bedeutet bei Klassen: Der übergebene Typ muss von der Klasse abgeleitet sein bzw. diese Klasse sein. • Bei Einschränkungen wird zum Übergang zum Rohtyp nicht Object, sondern der hier angegebene Klassentyp benutzt. 27 Komponenten – WS 2014/15 – Teil 5/Generics Einschränkungen III class Ober { void meth() {...} } class Unter1 extends Ober { void meth() {...} } class Unter2 extends Ober { void meth() {...} } class MyClass<T extends Ober>{ void myMethod (T obj) { ... obj.meth(); ... } } • In diesem Beispiel darf als T nur Unter1, Unter2 und Ober benutzt werden. Komponenten – WS 2014/15 – Teil 5/Generics 28 Einschränkungen IV class MyClass<T extends Ober>{ void myMethod (T obj) { ... obj.meth(); ... } } • class MyClass<T> ... führt zum selben Rohtyp wie class MyClass<T extends Object>, d.h. der Compiler setzt es nach Object um. • Bei class MyClass<T extends Ober> setzt der Compiler es nach dem Typ Ober um. Der generierte Rohtyp ist immer die Obergrenze innerhalb der Vererbungshierarchie. Bisher hatte wir immer Object gehabt. Komponenten – WS 2014/15 – Teil 5/Generics 29 Hinweis (1) class MyClass<T> ... (2) class MyClass<T extends Ober> ... Welche Methoden können in der Klasse MyClass aufgerufen werden? • Da in (1) alle Klassen als Parameter benutzt werden können, nur die Methoden der Klasse Object - und dass ist nicht besonders viel. • Selbst wenn nur als aktuelle Parameter die Klassen eines eingeschränkten Klassenbaums benutzt werden und daher mehr Methoden zur Laufzeit zu Verfügung stehen, können diese nicht verwendet werden. • Bei (2) können alle Methoden des "Vererbungsweges" von der Klasse Object herunter bis zur Klasse Ober benutzt werden, nicht jedoch Methoden aus Klassen, die von Ober abgeleitet wurden. Komponenten – WS 2014/15 – Teil 5/Generics 30 Einschränkungen IV (3) ... Klassenname<T extends N1 & N2 & N3...> { ... class MyClass<T extends Ober & Comparable<T>>{ ... } • Wenn sich die Bedingung auf mehrere Klassen bezieht, so wird einfach eine Liste mit dem Trenner & angegeben. • Da es keine Mehrfachvererbung gibt, dürfen in der Liste nur eine Klasse, aber mehrere Interfaces stehen. • Es muss zuerst die Klasse und dann die Liste der Interfaces angegeben werden, also N1 ist eine Klasse oder ein Interface, während N2 bis NN nur Interfaces sein können. Komponenten – WS 2014/15 – Teil 5/Generics 31 Noch einmal Vererbung I class MyClass<T> extends UpperClass {...} UpperClass obj1= new MyClass<Integer>(); UpperClass obj2= new MyClass<String>(); class MyClass<T> extends Queue<String> {...} Queue<String> obj3= new MyClass<Integer>(); Queue<String> obj4= new MyClass<String>(); • Die obigen Beispiele sind alle zulässig. • Generische Klassen können fast wie normale Klassen behandelt werden. Komponenten – WS 2014/15 – Teil 5/Generics 32 Noch einmal Vererbung II class MyClass<T> extends UpperClass<T> {...} UpperClass<Integer> obj1= new MyClass<Integer>(); UpperClass<Integer> obj2= new MyClass<String>(); // OK // verboten class MyClass extends Queue<String> {...} Queue<String> obj3= new MyClass(); MyClass obj4= new MyClass(); • Eine nicht-generische (also normale) Klasse darf nur dann von einer generischen Klasse erben, wenn diese ausgeprägt ist. • Eine generische Klasse kann nur dann von einer anderen generischen Klasse erben, wenn die Parametertypen übereinstimmen, ansonsten entstehen Probleme bei der Typsicherheit. Komponenten – WS 2014/15 – Teil 5/Generics 33 Noch einmal Vererbung III class MyQueue extends Queue<String> { ... public void enqueue(String elem) { ... } class MyQueue extends Queue<T> { ... public void enqueue(T elem) { ... } • Sollen Methoden in einer Unterklasse überschrieben werden, so müssen die Signaturen entsprechend der Parametrisierung übereinstimmen. • Bei einer ausgeprägten Klasse darf dann nicht mit Typparametern gearbeitet werden, bei einer generischen mit dem Typparameter. Komponenten – WS 2014/15 – Teil 5/Generics 34 Laufzeitabfragen und Prüfungen (1) (2) (3) (4) (5) Object obj= new Queue<String>(); if (obj instanceof Queue<String>) .... if (obj instanceof String) ... if (obj instanceof Queue) ... Queue<String> neu= (Queue<String>)obj; // // // // Verboten OK OK Warnung • (2) ist deshalb verboten, weil zur Laufzeit keine Informationen über den Typparameter vorhanden sind, m.a.W. generische Klassen existieren nur zur Compile-Zeit. • Die Abfrage nach den Rohtypen ist aber erlaubt und liefert die gewünschten Resultate. • Ein cast zurück in die Welt der ausgeprägten generischen Klassen wird immer mit einer Compiler-Warnung bestraft, da dieser dies nicht prüfen kann. Komponenten – WS 2014/15 – Teil 5/Generics 35 Wildcards I Queue<String> obj1= new Queue<String>(); Queue obj2= new Queue<String>(); Queue<?> obj3= new Queue<String>(); Queue<?> obj4= new Queue<Integer>(); Queue<?> obj5= new Queue<Queue>(); • Ein konkreter Typ ist nur zu sich selbst kompatibel und zu seinem Rohtyp. • Ein konkreter Typ ist daher nie zu einem anderen konkreten Typ kompatibel. • Damit sind keine Objekte möglich, die mit der Menge aller unterschiedlich ausgeprägter Typen kompatibel sind, m.a.W. eine Collection von verschiedenen Queues lässt sich nicht realisieren, es sei denn... Es wird der Wildchard-Operator ? benutzt. Komponenten – WS 2014/15 – Teil 5/Generics 36 Wildcards II (1) Queue<?> obj1= new Queue<String>(); (2) Queue<Integer> obj2= obj1; // verboten (3) Queue<String> obj3= obj1; // verboten! • Queue<?> ist die Obermenge zu Queue<Integer>, Queue<String>, Queue<Blabla> etc. • Damit lässt sich nicht viel anfangen, da ein Weg zurück zu ausgeprägten Klassen nicht möglich ist. Warum? • Zeile (3) wäre in Ordnung, aber nach Akzeptieren der Zeile (2) würden Integer-Objekte in der Warteschlange sein, die in Wirklichkeit Strings sind. • Daher wird einfach alles verboten. Grund: Es würde Überprüfungen zur Laufzeit erfordern. 37 Komponenten – WS 2014/15 – Teil 5/Generics Wildcards III (1) (2) (3) (4) (5) Queue<? extends Ober> obj; obj= new Queue<Ober>(); obj= new Queue<Unter1>(); obj= new Queue<Unter2>(); obj= new Queue<Integer>(); // // // // OK OK OK verboten • Die offene Spezifikation durch ? kann durch eine Liste von Klassen hinter extends beschränkt werden, so dass Untermengen von Klassen als Parameter möglich sind. • Beispiel: Sie wollen Warteschlangen für wichtige Ereignisse von denen für unwichtige trennen: Queue<Alert> und Queue<Important> von Queue<Message>. Dies lässt sich über eine Vererbungshierarchie mit Alert, Important und Message realisieren. Komponenten – WS 2014/15 – Teil 5/Generics 38 Generische Methoden I Wie lässt sich eine allgemeine Methode zur Bestimmung des maximalen Wertes zweier Werte implementieren? public <T extends Comparable<T>> T max(T left, T right) { if(left.compareTo(right)>0) { return left; } else { return right; } } • Das <>-Konstrukt muss vor dem Rückgabetyp stehen. • Dieselben Buchstaben bedeuten auch derselbe Typ. • Das extends... besagt, dass alles erlaubt ist, was das Interface ... implementiert, m.a.W. was compareTo() realisiert. Komponenten – WS 2014/15 – Teil 5/Generics 39 Generische Methoden II (1)String m= max("Albert Einstein", "Fritz Walter"); (2)int w= max(10,-10); (3)Queue<Integer> q1= new Queue<Integer>(); (4)Queue<Integer> q2= new Queue<Integer>(); (5)Queue<Integer> qint = max(q1, q2); • (1) liefert natürlich "Fritz Walter", was beweist, dass Fußball größer als Physik ist. (Java hat immer recht) • (2) liefert 10. • Und (5) einen Compiler-Fehler, da unsere Queues nicht das Interface Comparable realisieren. Komponenten – WS 2014/15 – Teil 5/Generics 40 Generische Methoden III public Comparable max(Comparable left, Comparable right) { if(left.compareTo(right)>0) { return left; } else { return right; } } • Dies hier ist eine alternative Implementierung ohne generische Methoden, die dasselbe wie oben leistet. • Warum? Weil die übergebenen Parameter eine gemeinsame Oberklasse haben bzw. hier implementieren. • Wenn das nicht gegeben ist, dann ist der generische Weg nicht nur der bessere, sondern auch der einzige. Komponenten – WS 2014/15 – Teil 5/Generics 41 Generische Methoden IV – Ein Beispiel public <T> void enqueueArray(T[] liste, Queue<T> q) { for(int i= 0; i<liste.length;i++) { q.enqueue(liste[i]); } } • Wenn eine Liste von Werten in eine Queue gebracht werden soll, so kann dies mit der obigen Methode erfolgen. • Dabei kann auf den 2. Parameter verzichtet und die Daten/Methoden der eigenen Instanz verwendet werden. • Dies ist einfach nur ein Beispiel, bei dem die betroffenen Klassen/Objekte außer Object keine gemeinsame Oberklasse haben. Komponenten – WS 2014/15 – Teil 5/Generics 42 Nach dieser Anstrengung etwas Entspannung... Komponenten – WS 2014/15 – Teil 5/Generics 43