1 / 34

Grundlagen der Informatik I Thema 18: Typkonvertierungen und Generizität

Grundlagen der Informatik I Thema 18: Typkonvertierungen und Generizität. Prof. Dr. Max Mühlhäuser Dr. Guido Rößling. Typkonvertierung. Mit statischer Typisierung gibt es viele Kontexte, in denen Werte eines bestimmten Typs erwartet werden.

abeni
Download Presentation

Grundlagen der Informatik I Thema 18: Typkonvertierungen und Generizität

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. Grundlagen der Informatik IThema 18: Typkonvertierungen und Generizität Prof. Dr. Max Mühlhäuser Dr. Guido Rößling

  2. Typkonvertierung • Mit statischer Typisierung gibt es viele Kontexte, in denen Werte eines bestimmten Typs erwartet werden. • In “a = expression” erwarten wir, dass expression den gleichen Typ wie a hat • In “a + b” erwarten wir, dass a und b entweder beide ganze Zahlen, Fließkommazahlen oder Zeichenketten sind. • In “f(a,b)” erwarten wir, dass die Typen der Argumente sich mit denen der formalen Parameter decken.

  3. Arten der Typkonvertierung in Java • Identitätskonvertierung – von einem Typ auf denselben Typ • Es ist z.B. ok, eine redundante Konvertierung einzufügen • Verbreiterung primitiver Typen • z.B. byte nach int (ohne Informationsverlust) • z.B. int nach double (möglicher Informationsverlust) • Verengung primitiver Typen • z.B. int nach byte (höhere Bits wegwerfen) • z.B. float nach int

  4. Arten der Typkonvertierung in Java • Verbreiterung von Referenztypen (Verallgemeinerung) • Gegeben Referenztypen A, B, dann kann A zu B verbreitert werden genau dann wenn A ein Subtyp vonB ist. • Diese Konvertierung geschieht immer während der Kompilierung und kann keine Laufzeitfehler auslösen. • Verengung von Referenztypen (Konkretisierung) • Gegeben Referenztypen A, B, dann kann A verengt werden zu B genau dann wenn B ein Subtyp von A ist. • Diese Konvertierung muss zur Laufzeit überprüft werden und löst möglicherweise einen Laufzeitfehler aus.

  5. Arten der Typkonvertierung in Java • String Konvertierung • Jeder Typ kann zu String konvertiert werden • Implizit wird die toString()-Methode (aus java.lang.Object) benutzt • Das haben Sie z.B. bei System.out.println(myObject)schon genutzt • Boxing und Unboxing • Von byte zu Byte, int zu Integer etc. und umgekehrt • Ungeprüfte Konvertierung • Konvertierungen die zu einem Fehler führen können, ergeben Warnung zur Kompilierzeit • z.B. Konvertierung eines “raw type” zu einem parametrisierten Typ

  6. Konvertierungskontexte • Zuweisungskonvertierung:v = expr • Konvertiere den Typ von expr zum Typ von v. • Nur für Konvertierungen ohne mögliche Laufzeitfehler • Methodenaufrufkonvertierung:expr.M(expr‘) • konvertiert die Typen der Argumente. • Nur für Konvertierungen ohne mögliche Laufzeitfehler • Cast-Konvertierung:(T)expr • Konvertiert den Typ von exprzu T • Kann auch die Verengungskonvertierung benutzen! • Numerische Konvertierung • Konvertiert die Operanden eines numerischen Operators in einen gemeinsamen Typen, so dass die Operation ausgeführt werden kann • erlaubt Identitäts-, Verbreiterungs- und Unboxing Konvertierung • 4 + 2.0  6.0

  7. Typ-Verengung • Verbreiterung verliert statische Typinformation // p bezieht sich auf Circle, Verbreiterung OKGraphicObject p = new Circle(5, 12, 4); Circle c1 = p; // compile-Fehler — kann nicht verengen • Die Information kann zur Laufzeit mit Tests und Casts wieder zurückgeholt werden kann if (p instanceof Circle) { // Laufzeittest c1 = (Circle) p; // Explizite Verengung} // zur Laufzeit OK

  8. Nochmals Collections… • Eine Collection sollte zu mehr als einem Typ passen • Eine Implementierung langt für verschiedene Zwecke • Das allgemeine Verhalten der Collection hängt nicht vom Elementtyp ab • Zusätzlich wollen wir einen spezifischenElementtypgarantiert haben • Angenommen, wir nutzen eine Collection nur für Person Instanzen • Dann wollen wir auch Person Objekte ohne Verendung von Objecterhalten können!

  9. Nochmals Collections… List myIntList = newLinkedList(); // 1 myIntList.add(new Integer(0)); // 2 Integer x = (Integer)myIntList.get(0); // 3 • Der Cast in Zeile ist nervig aber erforderlich • Java garantiert nur, dass das Ergebnis zum Typ Object passt • Es kann also vom Typ Integer sein – oder eben auch nicht! • Daher kriegen wir einen Typfehler ohne den Cast • Zudem wurde der Cast nicht mit instanceofabgesichert • Wir könnten uns auch irren und etwas anderes erhalten List myIntList = newLinkedList(); // 1 myIntList.add("Hello World"); // 2’ Integer x = (Integer)myIntList.get(0); // 3 • Nun erhalten wir eine ClassCastException zur Laufzeit!

  10. Angabe der Absichten mit Generics • Wir müssen Java mehr über unsere Absichten sagen • “Diese Liste will nur Elemente haben, die zu Integer passen” • Dafür müssen wir…: • Die Deklaration der Liste anpassen: List<Integer> • Das Anlegen der Liste anpassen: newLinkedList<Integer> • Vorteile: • Wir können auf den Cast in Zeile 3 verzichten • Typen, die nicht zu Integer passen, können nicht eingefügt oder abgefragt werden List<Integer> myIntList = newLinkedList<Integer>(); // 1 myIntList.add(new Integer(0)); // 2 Integer x = myIntList.get(0); // 3 myIntList.add("Hello World"); //compile-Fehler

  11. Was hat sich geändert? • myIntList ist nun eine List<Integer> • Nicht „eine“ Liste, sondern eine Liste von Integer-Objekten • Wir müssen Ergebnisse also nicht mehr nach Integercasten • Wir können keine anderen Typen mehr speichern • Darum kümmern sich der Java Compiler (und Eclipse) • myIntList.add("Hello World"); • The method add(Integer)in the type List<Integer> is not applicable for the arguments (String)

  12. Und was bringt uns das? • Auf den ersten Blick vielleicht nicht viel • Die Deklaration und Erzeugung der Liste sind länger • Wir können nur auf den Cast verzichten • Es gibt aber einen großen Unterschied! • Nun kann der Compiler die Typkorrektheit überprüfen • Die Deklaration von myIntList gibt den Typ an • Der Compiler stellt sicher, dass alle Zugriffe zum Typ passen • Was ist hier anders als bei einem Cast? • Mit einem Cast sagt der Programmierer “der Typ sollte hier zum Cast zu Typ X passen” • Das sollte immer mit instanceofabgesichert werden! • Generische Deklaration sagt “das passt immer zu Typ X“

  13. Nochmals Listen List und Iterator werden in java.util wie folgt definiert: public interface List<E> { void add(E x); Iterator<E> iterator(); } publicinterface Iterator<E> { E next(); boolean hasNext(); void remove(); } • Das kennen wir im Wesentlichen schon (siehe T15) • Außer dem <E> und E • Damit wird ein formaler Typparameterdeklariert • E kann dann in der Klasse als Typ genutzt werden

  14. List<Integer> und IntegerList • Wir nutzen List<Integer>, aber es gibt nur List<E>? • E ist ein formaler Typparameter • Ähnlich zu den formalen Parametern in Methodensignaturen • E wird in allen Aufrufen durch Integer ersetzt • Intuitiv kann man sich das so vorstellen: publicinterfaceIntegerList { voidadd(Integer x); IntegerIteratoriterator(); } • Das kann das Verständnis erleichtern • Aber es ist auch irreführend • Es gibt nur eine List Klasse, nicht “eine pro Typ”

  15. Generics und Subtypen • Warum ist der folgende Code nicht korrekt? • Zeile 1 ist sicherlich korrekt • LinkedList ist ein Untertyp von List • Die formalen Parameter (beide String) „passen“ • Passt List<String> in Zeile 2 zu List<Object>? • List ist auf beiden Seiten die gleiche Basisklasse • String ist ein Erbe von Object • Die intuitive Antwort ist also „ja” List<String> ls = newLinkedList<String>(); // 1 List<Object> lo = ls; // 2 lo.add(newObject()); // 3 String s = ls.get(0); // 4 Dieser Code wird vom Übersetzer so nicht akzeptiert!

  16. Generics und Subtypen • Zunächst schauen wir uns den weiteren Code an: List<String> ls = newLinkedList<String>(); // 1 List<Object> lo = ls; // 2 lo.add(newObject()); // 3 String s = ls.get(0); // 4 Dieser Code wird vom Übersetzer so nicht akzeptiert! • Zeile 2 legt ein Alias der Object und String List an • Wir können ein Object in List<Object> einfügen (Zeile 3) • Aber wir fügen es gleichzeitig auch in lsein! • ls ist deklariert als List<String>… • Aber lswürde nun auch andere Objekttypen enthalten • Der Compiler erkennt das nicht, wenn Zeile 2 korrekt ist • Daher wird der Compiler Line 2 zurückweisen • “type mismatch: cannotconvertfromList<String>toList<Object>”

  17. Generics, Subtypen und unsere Intuition • Sei Y eine Unterklasse von (Klasse oder Interface) X • Gsei eine generische Typdeklaration (z.B. List<E>) • G<Y>ist kein Subtyp von G<X> • Warum fällt uns das so schwer zu glauben? • Wir unterstellen, dass sich die Collection nicht ändert • Das ist natürlich oft eine falsche Annahme! • Angenommen, das Studierendensekretariat gibt eine Liste der Studenten an das Einwohnermeldeamt • Ein Student ist eine Person • Übergabe von List<Student> für List<Person>wirkt OK • Aber nur, wenn eine Kopie übergeben wird, keine Referenz! • Das Einwohnermeldeamt fügt eine Person (aber nicht Student) ein • Die gemeinsame (!) Studentenliste ist nun korrumpiert

  18. Generics, Subtypen und Intuition Collection<Vehicle> Collection<Car> Punktweise Subtypen, sicher Set<Vehicle> Set<Car> Kovariante Subtypen nicht sicher

  19. Behandlung von Subtypen: Wildcards • Wir wollen alle Elemente einer Collection ausgeben • Ohne Generics funktioniert das in etwa wie folgt: • Jetzt passen wir das eben schnell an Generics an: • Aber diese Methode akzeptiert nurCollection<Object> • Sie akzeptiert keineList<Person>, sondern nur List<Object> • Zur Erinnerung: List<Person> ist kein Subtypvon List<Object> voidprintCollection(Collection c) { for (Objectelement: c) System.out.println(element); } void printCollection(Collection<Object> c) { for (Object element: c) System.out.println(element); }

  20. Behandlung von Subtypen: Wildcards • Was müssen wir nun tun? • Wir brauchen einen gemeinsamen Obertypfür alle Collection-Arten • Das ist die “Collection von unbekannten Elementtypen” • In Java-Notation: Collection<?> (hier: List<?>) • Nun können wir den Code anpassen: • Die Elemente der Liste werden wie Objectbehandelt • Da java.lang.Object die Superklasse aller Typen ist • Daher passt jeder Typ – auch der „unbekannte Typ ?“ - zu Object voidprintCollection(Collection<?> c) { for (Objectelement: c) System.out.println(element); }

  21. The unbekannte Typ „<?>“ • Der „Unbekannte Typ“ <?> wirkt sehr hilfreich • Wir können endlich auf die Elemente zugreifen! • Falls nötig, können wir sie auf einen konkreten Typ casten • Natürlich nur wenn der Cast gültig ist! • Wie steht es mit dem folgenden Code? • c hat den “unbekannten” Elementtyp “?” • Der einzufügende Typ muss zu “?” passen • Der Compiler kennt den tatsächlichen Typ von “?” nicht • Daher ist unklar, ob Object dazu passt • Man darf kein Object einfügen in eine List<String>! • Es kann nur der Wert null eingefügt werden Collection<?> c = new ArrayList<String>(); c.add(newObject()); Dieser Code wird vom Übersetzer so nicht akzeptiert!

  22. Eine generische Zeichenanwendung • Wir betrachten folgendes Modell für ein Zeichentool: abstractclassShape { abstractvoiddraw(Canvas c); } class Circle extends Shape { privateint x, y, radius; voiddraw(Canvas c) { // ... } } classRectangleextends Shape { privateint x, y, width, height; voiddraw(Canvas c) { // ... } } classCanvas { voiddraw(Shape s) { s.draw(this); } }

  23. Eine generische Zeichenanwendung • Jede Form erweitert Shape und kann sich zeichnen • Wir werden meist mehr als eine Form zeichnen! • Also speichern wir die Elemente in einer List<Shapes> • Wir passen den Code von Canvasentsprechend an: • Das funktioniert gut für jede List<Shape> • Und wenn wir eine List<Circle> zeichnen wollen? • Circle ist ein Subtyp von Shape • Aber List<Circle> ist kein Subtyp vonList<Shape> • Der Aufruf drawAll(List<Circle>) führt zu einem Compile-Fehler voiddrawAll(List<Shape> shapes) { for(Shape s: shapes) { s.draw(this); } }

  24. Eine generische Zeichenanwendung • drawAll passt an sich zu allen Shape-Subtypen • drawAll sollte mit List<Circle>, List<Shape>, List<Rectangle>, funktionieren…! • Der Einsatz von “?” bringt uns nicht weiter • Der unbekannte Typ ? hat keine Methode “draw(Canvas)” • Der Cast nach Shape ist möglich aber gefährlich • Wir müssen die Wildcard beschränken (“bound”) • Sie sollte alle Listen von Subtypen von Shape akzeptieren • In Java-Notation: voiddrawAll(List<? extends Shape> shapes) { for(Shape s: shapes) { s.draw(this); } }

  25. Beschränkte Wildcards • Es gibt einen kleinen aber wichtigen Unterschied • List<Shape> akzeptiert nur genau List<Shape> • List<?extends Shape> akzeptiert Listen von allen Subtypen von Shape, inklusive Shape selbst • List<Shape> • List<Circle> • List<Rectangle> • … • “? extends X” bedeutet: • Wir kennen den exakten Type nicht ( “?”) • Aber wir wissen, dass der Typ zu X konform sein muss • X ist die „obere Schranke“ der Wildcard

  26. Beschränkte Wildcards • Warum stimmt am folgenden Code nicht? • Wir können kein Rechteck in List<? extends Shape> einfügen • Wir kennen den exakten Typ der Elemente in shapes nicht • Wir wissen zwar, dass es ein Subtyp von Shape ist • Aber der Typ passt nicht unbedingt zu Rectangle • Der Aufruf könnte lauten addRectangle(List<Circle>); • Bei unbekanntem Typ können wir nur null einfügen • Einerseits schlecht… • Dafür gibt es keine Typprobleme zur Laufzeit! • Java geht hier den (typ-)sicheren Weg. voidaddRectangle(List<? extends Shape> shapes) { shapes.add(0, newRectangle()); } Dieser Code wird vom Übersetzer so nicht akzeptiert!

  27. Schreiben von generischen Methoden • Wie kopiert man alle Arrayelemente in eine Collection? • Wir können nicht Collection<Object> als Parameter nutzen • Aber Collection<?> wird auch nicht funktionieren! • Der Typ ist unbekannt, also passt jeder konkrete Typ potentiell nicht  static void copyToCollection(Object[] array, Collection<?> c) { for (Object o: array) { c.add(o); } } Dieser Code wird vom Übersetzer so nicht akzeptiert!

  28. Schreiben von generischen Methoden • Dazu schreiben wir eine generische Methode: • Das <T> besagt, dass dies eine generische Methode ist • T wird vor dem Rückgabewert eingeführt • T dient als formaler Parametertyp • Der Java Compiler nutzt das für Zugangskontrolle • Der Typ des Feldes und der Collection müssen konform sein • Es gilt der allgemeinste konforme Typ • Meist langt die Nutzung von Wildcards • Wildcards werden bevorzugt – klarer und knapper static <T> voidcopyToCollection(T[]array, Collection<T> c) { for (T o: array) { c.add(o); } }

  29. Abfrage des Typs • Abfrage des Basistyps eines generischen Typs: • Aber wir erhalten nicht den genauen generischen Typ • Es gibt nur eine Klasse List (siehe Folie 15) • Die compile-Zeit Information über den formalen Parameter ist zur Laufzeit nicht verfügbar • Wir können auch getClass() nutzen: • Die Ausgabe ist wieder true – getClass() liefert java.util.List List<Circle> circles = new ArrayList<Circle>(); List<Rectangle> rects = new ArrayList<Rectangle>(); System.out.println(circlesinstanceof List); //  true System.out.println(circlesinstanceof List<Circle>); // Fehler System.out.println(circles.getClass() == rects.getClass());

  30. Untere Schranken • Wir wollen eine Datensenke implementieren • Diese leert eine Collection und gibt das letzte Element zurück • Was ist hier das generische Typargument <T>? • Es gibt keinen gültigen Typ <T>, da die Typen nicht konform sind • Der Aufruf in der letzten Zeile ist damit illegal interface Sink<T> { voidflush(T t); } static<T> T flushAll(Collection<T> c, Sink <T> sink) { T last = null; for (T t: c) { last = t; sink.flush(last); } return last; } Sink<Object> sink; Collection<String> collection; String lastString = flushAll(collection, sink); Dieser Code wird vom Übersetzer so nicht akzeptiert!

  31. Untere Schranken • Das scheint einfach zu beheben zu sein…: • Jetzt funktioniert der Aufruf! • Aber: T wird nun auf Object abgebildet • Weil das der Elementtyp des Sinks ist • Daher scheitert die Zuweisung des Ergebnisses an String • Wir benötigen “unbekannt, aber Oberklasse von T” • In Java-Notation: <? super T> static <T> T flushAll(Collection<? extends T> c, Sink<T> sink) { static <T> T flushAll(Collection<T> c, Sink<? super T> sink) {

  32. Implementierungsbeispiel interface Sink<T> { voidflush(T t); } classConcreteSink<T>implementsSink<T> { publicvoidflush(T t) { System.err.println("Flushing " + t + ", type: " +t.getClass().getName()); } } static<T> T flushAll(Collection<T> c, Sink <? super T> sink) { T last = null; for(T t: c) { last = t; sink.flush(last); } returnlast; } Sink<Object> sink = newConcreteSink<Object>(); Collection<String> cs = Arrays.asList("a","bb2","cdf"); System.err.println(flushAll(cs, sink)); Bitte denken Sie sich den jeweiligen Klassenkontext dazu!

  33. Statische Typisierung - Zusammenfassung • Statische Typsysteme sind Gegenstand sehr aktiver Forschung • Java-ähnliche Typsysteme sind begrenzt, aber im Allgemeinen können Typsysteme sehr mächtig und ausdrucksstark sein • Aber auch sehr kompliziert • Manche Programmierer sehen statische Typsysteme als eine Begrenzung ihrer Freiheit ("ich weiß, was ich tue") • Andere Programmierer denken, dass statische Typsysteme nicht nur viele Fehler erkennen, sondern auch eine gute Struktur im Code erzwingen ("erst denken, dann schreiben") • Die Diskussion ist noch nicht beendet. • Sie sollten eine fundierteMeinung darüber haben!

  34. Weiterführende Literatur • Die folgenden Materialien bieten (viel) mehr zu Generics: • Java Tutorial on Generics (byGiladBracha): http://java.sun.com/docs/books/tutorial/extra/generics/ • GiladBracha, “Generics in the Java Programming Language” • Diente als Hauptvorlage für diese Folien • http://java.sun.com/j2se/1.5/pdf/generics-tutorial.pdf • Maurice Naftalin, Philip Wadler: “Java GenericsandCollections”, O’Reilly, 2006

More Related