1 / 54

Prof. Dr. Max Mühlhäuser Dr. Guido Rößling

Grundlagen der Informatik I Thema 17: Statische Typisierung Subtyp-Polymorphie. Prof. Dr. Max Mühlhäuser Dr. Guido Rößling. Worum geht es in dieser Vorlesung?. Das “magische Dreieck” der Programmiersprachen. Welche Invarianten gibt es in meinem Programm?. sicher. mächtig. einfach.

lamya
Download Presentation

Prof. Dr. Max Mühlhäuser Dr. Guido Rößling

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 17: Statische Typisierung Subtyp-Polymorphie Prof. Dr. Max Mühlhäuser Dr. Guido Rößling

  2. Worum geht es in dieser Vorlesung? • Das “magische Dreieck” der Programmiersprachen Welche Invarianten gibt esin meinem Programm? sicher mächtig einfach Welche Ideen können in der Sprachedirekt ausgedrückt werden? Wie einfach ist ein Programmzu verstehen? Wir hatten diese Abwägung schon mal:Die Diskussion pro/contra Zuweisungen

  3. Werte und Typen • Definition: Ein Typ ist eine Menge von Werten, die irgendeine interessante gemeinsame Eigenschaft haben • Gemeinsame Eigenschaften  gemeinsame Operationen! • Ist ein Wert v Element eines Typs T, sagen wir v hat den Typ T • Beispiel: {0,1,2,…} ist ein Typ: die natürlichen Zahlen • Beispiel: {1,2,3,4,5,6} ist ein Typ: die Menge der deutschen Schulnoten • Ein Wert kann zu mehreren Typen passen • Andere Typen: Symbol, String, …

  4. Werte und Typen • Viele Abstraktionsmechanismen in Programmiersprachen beruhen auf der Abstraktion Wert  Typ • Wir abstrahieren (* 4 4), (* 3 3) zu (square 4), (square 3), wobei square = (lambda (x) (* x x)) • Wir gehen implizit davon aus, dass das x in der Funktionsdefinition den Typ Number hat, denn nur für diesen ist * definiert • Funktionen haben ebenfalls einen Typ, den wir mit Hilfe von Verträgen ausgedrückt haben • z.B. (lambda (x) (* x x)) hat den Typ number number

  5. Typfehler • Ein Typfehler tritt auf, wenn eine berechnende Einheit (z.B. ein Wert oder eine Funktion) in einer zu dem Konzept, das sie darstellt, inkonsistenten Art und Weise benutzt wird • Hardwarefehler • unerlaubter Instruktionsfehler • unerlaubter Speicherverweis • Versehentliche Semantik • z.B. kann inint_add(3, 4.5) der Wert 4.5 falsch interpretiert werden als ein Ganzzahlwert, der nicht mit 4.5 in Beziehung steht

  6. Typsysteme • Ein Typsystem ist ein Mechanismus, der jedem Wert einen Typ (oder Menge von Typen) zuordnet und verhindert, dass Typfehler auftreten. Beispiel: Scheme verhindert den Typfehler durch Abbruch des Programms Diese Fehlermeldung bekommen wir aber erst, wenn wirdas Programm ausführen! > (define x 'hello) > (+ x 5) +: expects type <number> as 1st argument, given: 'hello; other arguments were: 5

  7. Statische Typsysteme • In einem statischen Typsystem kann man ein Programm vor der Ausführung auf mögliche Typfehler überprüfen • Ein großer Vorteil, weil man den Fehler bemerkt, bevor es zu spät ist • Zuverlässiger als “Testen”, weil Tests nur die Anwesenheit, aber nicht die Abwesenheit von Fehlern überprüfen können • In kompilierten Sprachen geschieht diese Typüberprüfung häufig während der Kompilierung • Auch interpretierte Sprachen können aber statische Typsysteme haben • Java ist ein Beispiel für statisch typisierte Sprachen

  8. Statische Typsysteme • Werden Typfehler erst zur Laufzeit erkannt, spricht man von dynamisch typisierten Sprachen • Werden Typfehler gar nicht erkannt, spricht man von untypisierten Sprachen • Beispiel: Assembler • In der Realität werden viele Programmiersprachen statisch geprüft, garantieren aber keine Sicherheit (manchmal schwaches Prüfen genannt) • Manche Sprachen haben ein statisches Typsystem, welches aber nicht alle Typfehler erkennt • “gut genug”, 80/20 Regel

  9. Ebenen von Typsicherheit

  10. Statisch typisierte Sprachen • Beispielsprachen: Java, C++, C#, Haskell, ML • Der Typ jeder Variable kann zur Kompilierzeit bestimmt werden • Die meisten statisch typisierten Sprachen erreichen dieses Ziel, indem sie explizite Deklarationen der Typen verlangen. Es ist in Java unmöglich, eine Variable zu deklarieren, ohne ihren Typ anzugeben. class Counter { int n; Counter(intvalue) { n = value; } //... }

  11. Statisch typisierte Sprachen • Statisch typisierte Sprachen mit impliziten Deklarationen verwenden vorgegebene Konventionen • FORTRAN: Variablen, die mit I, J, K, L, M, N anfangen, enthalten Ganzzahlen • Es gibt statisch typisierte Sprachen mit Typdeduktionsfähigkeiten (type inference) • Gegeben folgende Deklaration, kommt der ML-Compiler zu der Folgerung, dass x eine Zahl ist. funsquare(x) = x * x

  12. Dynamisch typisierte Sprachen • In dynamisch typisierten Sprachen wird der Typ einer Variable dynamisch gebunden • Es sind keine Typdeklarationen notwendig. • Sehr bequem! Ermöglicht die Definition von flexiblen Funktionen, für die der Typ der Argumente egal ist. • Beispiele: • Scheme, Smalltalk, Self, Python, etc.

  13. Typprüfung • Typprüfung stellt sicher, dass die Operanden eines Ausdrucks kompatibel mit den Operatoren sind • Sowie dass es sich bei einer Nachricht an einem Objekt um eine Methode in der Schnittstelle des Objektes handelt • Kompatible Typen sind entweder die gleichen Typen, oder können implizit ineinander konvertiert werden. • Automatische Typkonvertierung wird coercion genannt • Siehe die Diskussion über automatische Typkonvertierungen in Abschnitt 2.5.2 SICP (V 9) • integer rational  real  complex • Ein Typfehler entsteht bei der Anwendung einer Operation an einem Operanden mit einem unerlaubten Typ.

  14. Typprüfung • Bei statischen Typbindungen kann die Typprüfung statisch (zur Kompilierzeit) stattfinden (statische Typprüfung). • Bei dynamischen Typbindungen kann die Typprüfung nur zur Laufzeit geschehen (dynamische Typprüfung). • In einigen statisch typisierten Sprachen können einige Typprüfungen auch nur zur Laufzeit durchgeführt werden • Mehr dazu später

  15. Statische vs. dynamische Typprüfung String x = "hello"; x = x * 10; Zur Kompilierzeit: x = x * 10; ^^^^^^ * not definedfor type String (define x 'hello) (+ x 1) Zur Laufzeit: +: expects type <number> as 1st argument, given: hello; other arguments were: 1 Ungültige Verwendung von Variablen werden von dem Compiler nicht entdeckt und führen zu einem Laufzeitfehler

  16. Statische vs. dynamische Typprüfung String s = newString("Hello World"); s.determineLength(); Compile program… Error in ... (line 3) s.determineLength(); ^^^^^^^^^^^^^^^^ MethoddetermineLength() not definedfor type String

  17. Statische vs. dynamische Typprüfung (define (make-counter n) (define (increment) (set! n (+ n 1)) n) (define (dispatchmsg) (cond ((eq? msg 'increment) increment) (else (error "undefoperation:" msg))) ) dispatch ) (define ca (make-counter 1)) (ca 'increment) (ca 'decrement) Zur Laufzeit: undefoperation: decrement

  18. Statisches Typisieren ist konservativ • Ein perfekter statischer Typprüfer wäre ein Typprüfer, der einen Typfehler dann und genau dann meldet, wenn der Typfehler während der Programmausführung auftreten wird • Ein perfekter Typprüfer existiert nicht! • Folgt aus bestimmten Unentscheidbarkeitsergebnissen aus der Theoretischen Informatik („Halteproblem“) • Somit nähern statische Typprüfer das Laufzeitverhalten an: Sie sind konservativ • Sie “bleiben auf der sicheren Seite” • Wann immer ein Typfehler zur Laufzeit auftreten würde, wird er vom Typprüfer entdeckt • Es gibt aber auch Programme, die vom Typprüfer abgelehnt werden, die ohne Fehler ausgeführt werden könnten

  19. Sollten Sprachen statisch geprüft werden? Argumente für statisches Prüfen • Wirtschaftlichkeit der Ausführung • z.B. arithmetisch, Methodenauflösung • Wirtschaftlichkeit des Kompilierens • separate Kompilierung möglich • Wirtschaftlichkeit der Entwicklung in kleinem Umfang • kann einen großen Teil von Routineprogrammierfehlern abfangen, besser als unvollständiges manuelles Testen • bietet Dokumentation zum Quelltext an • Gebrauch des Typprüfers als Entwicklungswerkzeug, z.B. eine Klasse umbenennen, um alle Referenzen darauf zu entdecken • Wirtschaftlichkeit der Entwicklung im großen Umfang • Teams können Schnittstellen vereinbaren, die vom Compiler erzwungen werden • Abstrahieren weg von der Implementierung großer Komponenten

  20. Sollten Sprachen statisch geprüft werden? Argumente gegen statisches Prüfen • Quelltext langatmiger • viele offensichtliche Deklarationen • Inflexibilität • Statisches Prüfen ist konservativ • probieren Sie das z.B. in Java • Identitätsfunktion implementieren ohne Typumwandlung • Das Argument von equals(Objectother) sollte den selben Typ haben wie der Empfänger • clone() gibt immer eine Instanz desselben Typs wie der Empfänger zurück • Viele Mechanismen wurden entwickelt, um mit diesen Problemen umzugehen • Typinferenz, Typsysteme höherer Ordnung, ... • Aber diese Mechanismen können sehr komplex sein Ein statischer Typprüfer ist ein mächtiger Helfer, solange die Programme in den Grenzen dessen liegen, was das Typsystem ausdrücken kann. Wenn wir über diese Grenzen hinausgehen wollen, steht uns das Typsystem im Weg.

  21. Statischer und dynamischer Typ • Der Typ eines Objekts ist immer die Klasse, deren Instanz es ist •  Dieser Typ ist immer dynamisch an ein Objekt gebunden. • Bei Variablen unterscheiden wir den statischen und dynamischen Typ: • Der statische Typeiner Variablen ist der Typ, an den sie bei der Deklaration gebunden wird. • Er verändert sich während der Ausführung nicht. • Der dynamische Typzu einem Zeitpunkt twährend der Ausführung ist der Typ des Objekts, auf das die Variable zum Zeitpunkt tverweist.

  22. Statischer und dynamischer Typ Der dynamische Typ vonshapeist ab diesem ZeitpunktRectangle Statischer Typ der Variablenshape Der dynamische Typ der Variablenshapeist zu diesem ZeitpunktCircle Shape shape= null; shape = newCircle(10, 20, 16); shape.draw(canvas); shape = newRectangle(10, 10, 20, 5); shape.draw(canvas); Hier wird in einem Programm ein und derselbe Name an verschiedenen Stellen benutzt, um zwei verschiedene Objekte zu bezeichnen. Eine solche Variable nennt man polymorph (griechisch "vielgestaltig"). In der realen Welt gibt es mehrere Objekte namens shape, und welches gemeint ist, hängt vom Kontext ab. In der Welt der grafischen Objekte ist es genauso.

  23. H Statischer und dynamischer Typ • Kann man ein grafisches Objekt beliebigen Typs an eine beliebige Variable zuzuweisen? • Natürlich nicht! Darum geht es ja bei Typsystemen: • Ein Typ regelt, wie ein Name benutzt werden kann • Der Typ schränkt die Menge der Werte ein, die einer Variablen zugewiesen werden können … und die darauf ausführbaren gültigen Operationen • Nur Instanzen einer direkten oder indirekten Subklasse ihres statischen Typs können an eine Variable zugewiesen werden. publicvoidmain(String[] args) { Circle shape= null; shape = newRectangle(10, 10, 20, 5); // ... }

  24. C A B Das Substitutionsprinzip Ein Objekt (grafisches Objekt) einer Subklasse kann überall dort verwendet werden, wo ein Objekt (grafisches Objekt) einer Superklasse erwartet wird. Diese Substituierbarkeitsrelation ist transitiv. Die DeklarationB var;impliziert: • An var kann jedes Objekt der Klassen B,C und all ihrer Subklassen zugewiesen werden (Transitivität der Substituierbarkeit). • Jede in Aund B deklarierte Operation kann auf var (auf jedem in var gespeicherten Objekt) aufgerufen werden. • Es ist nicht erlaubt, auf var eine Operation aufzurufen, die in C deklariert ist, aber nicht in A oder B.

  25. Das Substitutionsprinzip • Ein Objekt einer Subklasse kann einem Objekt einer Superklasse als Repräsentant dienen: • Seine Struktur enthält alle Attribute eines Objekts der Superklasse • Es kann alle Operationen ausführen, die für Objekte der Superklasse definiert wurden • Das Gegenteil trifft jedoch nicht zu! • Ein Objekt einer Superklasse ist nicht spezialisiert genug, um die Rolle des Objekts einer Subklasse zu spielen. Die DeklarationB var;heißt nicht “var hat genau den Typ B“; sondern “das Verhalten von var ist konform zu B“.

  26. Substitutionsprinzip: eine Analogie • Ein Kunde einer Autovermietung bestellt ein vierrädriges Fahrzeug zum Transport einiger Möbelstücke von A nach B • Es ist in Ordnung, wenn er einen Kleinbus erhält. Ein Kleinbus hat vier Räder. • Hat er jedoch einen Lastwagen bestellt, dann ist es nicht OK, ihm ein beliebiges vierrädriges Fahrzeug zu geben • Der Kleinbuskönnte zu wenig Ladefläche haben. Fahrzeug Zweirädrig Vierrädrig Kleinbus Motorrad Fahrrad Lastwagen

  27. Subtyp-Polymorphie Shape shape= null; shape = newCircle(10, 20, 16); shape.draw(canvas); shape = newRectangle(10, 10, 60, 20); shape.draw(canvas); Gleicher Programmtext, aber verschiedene Bedeutung POLYMORPHIE

  28. Jedes grafische Objekt befolgt Befehle gemäß seiner Programmierung: paint(g); Circle vs. Rectangle Dies ist besonders interessant, wenn mehrere grafische Objekte dieselben Befehle erhalten, sie aber unterschiedlich implementieren. Subtyp-Polymorphie

  29. Noch einmal Subtyp-Polymorphie Nun, da wir Schnittstellen kennen, betrachten wir statische und dynamische Typen von Variablen, Subtyp-Polymorphie und Substituierbarkeit gemeinsam. Zu diesem Zweck werden wir das UML-Klassendiagram auf dieser Folie benutzen. Es zeigt eine einfache Typ- und Klassenhierarchie.

  30. Noch einmal Subtyp-Polymorphie Das Modell stellt fünf Typen dar:vier Klassen und eine Schnittstelle. Obwohl das Modell "Klassendiagramm" genannt wird, ist es eigentlich ein Typdiagramm. Jede Java-Klasse und Schnittstelle deklariert einen benutzerdefinierten Datentyp.

  31. Noch einmal Subtyp-Polymorphie Von einem implementierungs-unabhängigen (also typorientierten) Standpunkt aus betrachtet, repräsentiert jedes der fünf Rechtecke einen Typ. Vom Standpunkt der Implementierung aus betrachtet, sind vier dieser Typen durch Klassen definiert, und einer durch eine Schnittstelle.

  32. Implementierungs-Hierarchie interfaceItype {  String m2(String s);  String m3();} class Base {public String m1() {return "Base.m1()";  }public String m2(String s) {return "Base.m2(" + s + ")";  }} classDerivedextends Base implementsItype {public String m1() {return"Derived.m1()";  }public String m3() {return "Derived.m3()";  }}

  33. Implementierungs-Hierarchie class Derived2 extendsDerived {public String m2(String s) {return "Derived2.m2(" + s + ")";  }public String m4() {return "Derived2.m4()";  }} class Separate implementsIType {public String m1() {return "Separate.m1()";  } public String m2(String s) {return "Separate.m2(" + s + „)";  } public String m3() {return "Separate.m3()";  }}

  34. Referenzen als Bullaugen Derived2 derived2 = new Derived2(); • Dieser Ausdruck tut zweierlei: er • deklariert die explizit getypte Referenzvariable derived2, • bindet derived2 an ein neu erzeugtes Derived2-Objekt. Es gibt ein Bullauge pro Operation im Typ Derived2. Das Derived2-Objekt bildet jede Derived2-Operation auf geeigneten Code ab, wie in der Implementierungs-Hierarchie vorgesehen. Die Derived2-Referenz kann als Menge von Bullaugen gesehen werden, durch die das Derived2-Objekt gesehen wird.

  35. Referenzen als Bullaugen Derived2 derived2 = new Derived2(); • Beispiel: • Das Derived2-Objekt bildet m1() auf Code ab, der in der Klasse Derived definiert ist. • Darüber hinaus überschreibt dieser Code die Implementierung von m1() in der Klasse Base. Eine Derived2-Referenzvariable kann auf die überschriebene Methode m1() in Base nicht zugreifen. Der implementierende Code in der Klasse Derivedkann aber die Implementierung in Base über super.m1() benutzen. Was die Referenzvariable derived2 angeht, ist dieser Code jedoch nicht sichtbar.

  36. Mehrere Referenzen auf ein Objekt Base base = derived2; ergibt eine weitere Menge von Bullaugen Substituierbarkeit: Wir können das Derived2-Object, das an die Referenz derived2 gebunden ist, mit jeder Variablen eines Typs T referenzieren, wenn Derived2 zu T konform ist. Die Typhierarchie ergibt, dass Derived, Base, und IType Supertypen von Derived2sind, Derived2also zu ihnen konform ist. Daher kann z. B. eine Base-Referenz an das von derived2referenzierte Objekt gebunden werden.

  37. Mehrere Referenzen auf ein Objekt • Es findet keine Veränderung an dem Derived2-Objekt oder seinen Operations-Abbildungen statt. • Werden m1() oder m2(String) auf derived2 oder base aufgerufen, wird jeweils derselbe Code ausgeführt. String tmp; tmp = derived2.m1();    // Derived2 reference // tmpis "Derived.m1()" tmp = derived2.m2("Hello"); // tmpis "Derived2.m2(Hello)"tmp = base.m1();      // Base reference // tmpis "Derived.m1()" tmp = base.m2("Hello");         // tmpis "Derived2.m2(Hello)"

  38. Mehrere Referenzen auf ein Objekt • Warum erhält man trotz verschiedener Referenzen identisches Verhalten? • Ein Objekt weiß nicht, wer oder was seine Methoden aufruft. • Ein Derived2-Objekt weiß nur, dass es, wenn es aufgerufen wird, den "Marschregeln" folgen muss, die von seiner Implementierungs-Hierarchie vorgegeben sind. • Methoden-Auswahl (method dispatch) • Diese Regeln legen fest, dass das Derived2-Objekt für die Methode m1() den in der Klasse Derived, und für m2(String) den in Derived2 definierten Code ausführen muss. • Die vom referenzierten Objekt ausgeführte Aktion hängt nicht vom Typ der Referenzvariablen ab.

  39. Mehrere Referenzen auf ein Objekt • Es findet keine Veränderung an dem Derived2-Objekt oder seinen Operations-Abbildungen statt. • Jedoch kann auf die Methoden m3() und m4() nicht mehr durch die Referenz base zugegriffen werden. String tmp;// Derived2 reference tmp = derived2.m3(); // tmpis "Derived.m3()"tmp = derived2.m4(); // tmpis "Derived2.m4()"// Base reference tmp = base.m3();   // Compile-time errortmp = base.m4();   // Compile-time error

  40. Mehrere Referenzen auf ein Objekt • Das Derived2-Objekt kann immer noch Aufrufe von m3() und m4() entgegen nehmen. • Typrestriktionen, die solche Aufrufe über die Base-Referenz verhindern, treten zur Compilezeit auf. • Die statische Typprüfung verhält sich wie ein Schild, indem sie Interaktionen zwischen Objekten nur durch explizit deklarierte Typ-Operationen erlaubt. • Die statischen Typen der Referenzen definieren die Grenzen der Objektinteraktion. • Das kann generalisiert werden: wird eine Supertyp-Referenz an ein Objekt gebunden, so wird dessen Benutzung eingeschränkt. • Warum sollte ein Entwickler sich dafür entscheiden, Objekt-Funktionalität zu verlieren?

  41. Mehrere Referenzen auf ein Objekt Diese Entscheidung wird oft unfreiwillig gefällt. Angenommen, eine Referenzvariable ref ist an ein Objekt gebunden, dessen Klasse folgende Methodendefinition enthält: public String poly1(Base base) {return base.m1();} Folgender Aufruf ist erlaubt, weil der Parametertyp konform ist: ref.poly1(derived2); Durch den Methodenaufruf wird eine lokale Base-Referenz an das übergebene Objekt gebunden.

  42. Mehrere Referenzen auf ein Objekt • Vom Standpunkt des Aufrufers, der das Derived2-Objekt übergibt, führt das Binden einer Base-Referenz durch den Implementierer von poly1(Base) zu einem Verlust von Funktionalität. • Für Implementierer sieht jedes an poly1(Base) übergebene Objekt wie ein Base-Objekt aus. • Den Implementierer kümmert es nicht, dass mehrere Referenztypen auf dasselbe Objekt zeigen können • Für ihn wird ein und derselbe Referenztyp an alle Objekte gebunden, die an die Methode übergeben werden. • Dass diese Objekte möglicherweise verschiedenen Typs sind, ist zweitrangig. • Der Implementierer erwartet nur, dass das jeweilige Objekt alle Operationen des Typs Base auf entsprechende Implementierungen abbilden kann.

  43. Eine Referenz auf mehrere Objekte • Interessantes polymorphes Verhalten tritt auf, wenn eine Referenzvariable nacheinander an mehrere Objekte ggf. unterschiedlichen Typs gebunden wird. • Streng genommen, meint Objekttyp genau den durch die Klasse des Objekts definierten Typ. Derived2 derived2 = new Derived2();Derivedderived = newDerived();Base base = new Base();String tmp;tmp = ref.poly1(derived2); // tmpis "Derived.m1()"tmp = ref.poly1(derived);  // tmpis "Derived.m1()"tmp = ref.poly1(base);     // tmpis "Base.m1()"

  44. Eine Referenz auf mehrere Objekte • Verschiedene Objekte werden durch dasselbe Bullauge betrachtet: • Das Bullauge definiert die Abbildungen, die verfügbar sein sollten • Verschiedene Objekte haben verschiedene Abbildungen.

  45. Die Macht der Polymorphie • Der Code von poly1(Base) betrachtet jedes Objekt durch eine Base-Typ-Linse. • Wird jedoch ein Derived2-Objekt übergeben, gibt die Methode ein Ergebnis zurück, das von Code in der Klasse Derived berechnet wurde! • Werden die Klassen Base, Derived oder Derived2 später erweitert, wird poly1(Base) problemlos Objekte der neuen Klassen akzeptieren und den erwünschten Code ausführen. • Polymorphie gestattet, dass die neuen Klassen lange nach der Implementierung von poly1(Base) hinzu kommen.

  46. Die Macht der Polymorphie Wie geht das? • Vom typorientierten Standpunkt aus ist der eigentliche Implementierungscode referenzierter Objekte unerheblich. • Der wichtigste Aspekt der Bindung von Referenzen an Objekte ist der, dass durch die zur Compilezeit stattfindende Typprüfung garantiert werden kann, dass das referenzierte Objekt für jede Operation des Typs eine zur Laufzeit verfügbare Implementierung besitzt. • Polymorphie befreit den Entwickler von der Pflicht, Implementierungsdetails einzelner Objekte zu kennen, und erlaubt statt dessen, einen Entwurf allein aus einer Typ-orientierten Perspektive anzufertigen. • Darin liegt ein signifikanter Vorteil der Trennung von Typ und Implementierung (auch genannt "Trennung von Schnittstelle und Implementierung").

  47. Java-Schnittstellen und Polymorphie • Java-Schnittstellen deklarieren benutzerdefinierte Typen • Entsprechend erlauben Java-Schnittstellen polymorphes Verhalten durch den Aufbau einer Typvererbungsstruktur. Angenommen, eine Referenzvariable ref wird an ein Objekt gebunden, dessen Klasse folgende Methode enthält: public String poly2(ITypeiType) {return iType.m3();} Das Folgende illustriert polymorphes Verhalten in poly2(IType): Derived2 derived2 = new Derived2();Separate separate = new Separate(); String tmp;tmp = ref.poly2(derived2); // tmpis "Derived.m3()"tmp = ref.poly2(separate); // tmpis "Separate.m3()"

  48. Java-Schnittstellen und Polymorphie • Vom Typ-orientierten Standpunkt aus betrachtet gibt es keinen Unterschied zwischen den Beispielen zu poly2 und poly1.

  49. Java-Schnittstellen und Polymorphie • Es gibt jedoch einen wichtigen Unterschied in der Implementierung: • In poly1(Base) werden durch die Base-Derived-Derived2-Klassenvererbungskette die benötigten Subtyp-Relationen etabliert, und das Überschreiben von Methoden führt zu den korrekten Abbildungen auf Implementierungscode. • In poly2(IType) tritt ein völlig anderes Verhalten auf. Die Klassen Derived2 und Separate teilen sich keine gemeinsame Implementierungshierarchie • Aber: Instanzen dieser Klassen stellen polymorphes Verhalten durch eine IType-Referenz an. Durch die Gruppierung von Objekten aus unterschiedlichen Implementierungshierarchien erlauben Java-Schnittstellen polymorphes Verhalten sogar in Abwesenheit gemeinsamer Implementierungen oder überschriebener Methoden.

  50. Schnittstelle und Typ eines Objekts • Was meinen wir mit der Schnittstelle eines Objekts? • Typischerweise bezeichnet das die Menge aller öffentlichen Methoden, die in der Klassenhierarchie des Objekts definiert werden: • Die Menge aller öffentlich verfügbaren Methoden, die auf dem Objekt aufgerufen werden können. Auf dem Bild bezieht sich die Schnittstelle zum Objekt auf die mit "Derived2 Object” bezeichnete Ebene, die alle verfügbaren Methoden für das Derived2-Objekt anführt.

More Related