Typisch Männer

Dieser Künne mal wieder. Ständig hat er einen frauenverachtenden Spruch auf den Lippen. Und jetzt hat er sie auch noch in digitalen Stein gemeißelt und als „Klausurvorbereitung“ verkauft. Dieser Sexismus ist wirklich nicht auszuhalten. Da möchte man harmlos ein paar Dinge erklären und er baut gleich diverse Stereotypen ein. <<create>> zum Beispiel, oder <<realize>>. Ein ganzes UML-Klassendiagramm mit Sticheleien gegen das andere Geschlecht. Und ein Sequenzdiagramm über angebliche Zicken, die Männer nur ausnutzen. Was das wieder für ein Licht auf uns Tutoren wirft…

Nach dem, was ich von der letzten Vorlesungsstunde gehört habe, wird die Klausur sehr einfach. Das soll natürlich niemanden in seinem Lernen bremsen. Ich habe den ganzen Kram schließlich schon ein paar Mal öfter gehört. Und die eine oder andere Frage scheint doch schon spezieller zu werden. Liskovsches Substitutionsprinzip und Synchronisation (niemals auf Immutables synchronisieren, wenn die verändert werden…) sollte man kennen, glaube ich. Generics sollten nach der heutigen Erklärung sogar Blondinen Männer verstanden haben.

Jetzt hoffe ich bloß noch, dass ich bei der Korrektur eine jungfräuliche Aufgabe erhalte und mich nicht nicht bei einer schon teilweise bearbeiteten dazusetzen muss. Das wäre schlimm, ich bewerte doch so gerne streng.

Generics, Wildcards und Bounds

Klarstellung: Im Informatik-B-Skript auf Seite 143 heißt es:

Es können zwei Arten von Typeinschränkungen (Bounds) unterschieden werden:
1. Upper Bounds (in Java durch Verwendung von „extends“): Der Typ muss der angegebenen Bound entsprechen oder eine Subklasse davon sein. Die Bound ist also in der Klassenhierarchie eine Beschränkung nach oben.
2. Lower Bounds (z.B. in Java: Box): Der Typ muss der angegebenen Bound entsprechen oder eine Superklasse dazu sein. Im Java-Beispiel wäre an der Stelle des ? der Typ Person oder die Superklasse Object möglich.

Hierbei wird der Eindruck erweckt, es sei möglich, den parametrisierenden Typen einer Klasse sowohl durch eine obere (upper) als auch eine untere (lower) Klasse in seiner Vererbungshierarchie beschränken. Dies ist nicht der Fall. Der Einsatz von Bounds bei der Klassendeklaration (public class Klasse<T>) ist beschränkt auf Upper Bounds. Es ist also nur möglich, das extends-Schlüsselwort zu verwenden. Auf diese Weise kann die Klasse zum Beispiel sicherstellen, dass sie bestimmte Methoden an den parametrisierten Objekten aufrufen kann. [Bounded Type Parameters]

Der Einsatz des super-Schlüsselwortes in seiner hier genutzten Bedeutung ist beschränkt auf die Erstellung von Referenzen parametrisierter Klassen. Hier ist es möglich, durch Klassek; eine Einschränkung zu erzwingen. Das benutzte ? nennt sich hierbei Wildcard. Solche Wildcards werden immer dann eingesetzt, wenn man mit parametrisierten Klassen arbeitet, von denen man sich nicht sicher sein kann, welchen Parameter sie besitzen werden.
Nötig sind solche Wildcards, weil die Auswertung von Generics statisch zur Compile-Zeit geschieht. Entsprechend muss ein Entwickler die Möglichkeit haben, die Typhierarchie weiter gefasst zu beschreiben, denn die Parameter werden auf Identität getestet. Das bedeutet, dass eine Codezeile wie Klasse<Object> k = new Klasse<Integer>(); zu einem Fehler führt. Korrekt muss es Klasse<? extends Object> k = new Klasse<Integer>(); oder Klasse<? super Integer> k = new Klasse<Integer&gt(); heißen.

Die praktischen Anwendungen für eine lower bound sind eher rar gesät. Upper bounds hingegen werden bei dynamischer Programmierung häufiger benötigt, wie schon das kurze Beispiel eben belegt. Der Fall einer upper bound wird wahrscheinlich der passendere sein.

Specifically, you learned that generic type declarations can include one or more type parameters; you supply one type argument for each type parameter when you use the generic type. You also learned that type parameters can be used to define generic methods and constructors. Bounded type parameters limit the kinds of types that can be passed into a type parameter; they can specify an upper bound only. Wildcards represent unknown types, and they can specify an upper or lower bound. During compilation, type erasure removes all generic information from a generic class or interface, leaving behind only its raw type.
Sun Java Tutorials

Veröffentlicht unter Info

Liskovsches Substitutionsprinzip

Es war im Jahre 1993, Barbara Liskov kam gerade aus dem Wal-Mart, als es ihr wie Schuppen von den Augen fiel:

Let q(x) be a property provable about objects x of type T. Then q(y) should be true for objects y of type S where S is a subtype of T.

Wann kommt nun der gemeine Programmierer mit dem Liskovschen Substitutionsprinzip in Berührung? Fragte man ihn, würde er müde mit dem Kopf schütteln und bestreiten, jemals etwas mit so einem Konstrukt zu tun gehabt zu haben. In Wirklichkeit hat er es aber wahrscheinlich einfach nur nicht gemerkt.

Was genau sagt das Substitutionsprinzip jetzt eigentlich aus?
Kurz zusammengefasst lässt es sich so sagen: Jeder Untertyp muss alle Funktionalitäten erhalten, die bereits der Obertyp bereitgestellt hat. Er kann also keine Fähigkeiten und Eigenschaften verlieren, sondern ausschließlich dazulernen. Sprechen wir nun über die Javawelt, fordert Frau Liskov also, dass jede Unterklasse mindestens die Methoden (strenger: Funktionalitäten) ihrer Elternklasse bereitstellen muss.

Wieso sollte man sowas tun?
Auch in der Welt der Javaprogramme ist es so, dass nicht immer alles gehalten wird, was man so verspricht. Die Möglichkeit, an die Stelle einer Klasse auch immer Instanzen von Subklassen treten zu lassen, zwingt uns praktisch dazu, uns im Codegerüst an das Substitutionsprinzip zu halten. Sähe unser Verstand vor, dass man mit einem Hund spazierengehen muss, käme es sicher zu einem Fehler, wenn wir dies mit einer Subklasse Chihuahua plötzlich gar nicht mehr ginge.

Und es geht gar nicht ohne?
Der B.Sc. würde sagen: Alles kann, nichts muss. Das Substitutionsprinzip ist – ja richtig – ein Prinzip, kein Gesetz. Nur eine strikte Vererbung würde die Umsetzung wirklich erzwingen können. In Java können wir durch Überschreiben die internen Funktionalitäten immer ändern: Gilt „Beim Spazierengehen entleert der Hund seine Blase“ (schöner Euphemismus) für alle Hunde, können wir dennoch das Spazierengehen beim Chihuahua so gestalten, dass er nur bellt und nicht pinkelt.
Anders ist es mit den „Grundfunktionen“, die ein Objekt zur Verfügung stellt, also den definierten Operationsschnittstellen. Ich kann in Java zwar, wie schon gesagt, interne Funktionalitäten entfernen, nicht aber die öffentlichen Deklarationen (um im Beispiel zu bleiben: auch wenn er nicht pinkelt, Spazierengehen muss man auch mit einem Chihuahua können). Eine einmal bereitgestellte Methodensignatur muss auch in allen Unterklassen des Objekts vorhanden sein.

Was sind Kovarianz, Kontravarianz und Invarianz?
Im Zusammenhang mit dem Substitutionsprinzip betrachtet man meistens die Varianzen in Eingabe und Rückgabe von Methoden. Was ist nun die genaue Bedeutung dieser Varianztypen?
Kovarianz bedeutet, dass sich ein Eingabeparameter (oder die Rückgabe) der Methode mit der Vererbungshierarchie ändert. Anders gesagt: Betrachte ich eine Subklasse meiner Klasse, so ist auch der Parameter eine Subklasse meines Eingabeparameters.
Kontravarianz ist das genau Gegenteil. Betrachte ich wieder den Eingabeparameter einer Methode, so ist dieser kontravariant, wenn in meiner Subklasse als Eingabeparameter eine Oberklasse meines Eingabeparameters benutzt wird. Für die Rückgabetypen gilt es analog.
Invarianz ist der einfachste Fall. Der Rückgabetyp (oder Typ des Parameters) einer Methode bleibt auch in der Subklasse exakt der gleiche.

Wo ist der Bezug zum Substitutionsprinzip?
Mit Hilfe der Varianzen lässt sich beschreiben, wie sich Eingabe und Rückgabe einer überschriebenen Methode ändern dürfen, ohne das Substitutionsprinzip zu verletzen, wenn wir Klassen ableiten. Es gilt dabei immer zu beachten, dass die Subklasse mit ihrer Methode genauso eingesetzt werden soll wie die Oberklasse.
Zuerst die Eingabeparameter: Bleibt der Parametertyp unverändert, ist natürlich alles in Ordnung, denn die Methode der Unterklasse kann exakt das verarbeiten, womit schon die Oberklasse umgehen konnte. Bei Kontravarianz ist dies ähnlich, hier hat man sogar einen Funktionszuwachs. Konnte die Methode zuvor nur eine bestimmte Klasse verarbeiten, kommen jetzt außerdem die Oberklasse und alle Schwesterklassen in Frage. Anders ist es bei der Kovarianz: Konnte ich zuvor alle Objekte einer bestimmten Klasse übergeben, ist es mir jetzt nur noch möglich, die Objekte einer bestimmten Subklasse bearbeiten zu lassen. Das würde das Substitutionsprinzip verletzen.
Beim Return-Typ verhält es sich ein wenig anders. Auch hier muss die Invarianz nicht näher betrachtet werden. Kovarianz und Kontravarianz hingegen vertauschen ihre Rollen. Hat mir die Methode der Oberklasse ein Objekt einer bestimmten Klasse zurückgegeben, bekomme ich bei der Kontravarianz nur noch ein allgemeineres Objekt. Wenn ich nun weiterarbeiten möchte, könnten mir plötzlich Methoden fehlen, die früher wie selbstverständlich vorhanden waren. Bei Kovarianz dagegen bekomme ich eine Unterklasse des ursprünglichen Rückgabetyps. Das bedeutet, alle ursprünglichen Methoden sind vorhanden, mit etwas Glück bekomme ich sogar ein Objekt, das noch viel mehr kann als zuvor. Kovarianz in der Rückgabe erfüllt also das Substitutionsprinzip.

Unterstützt denn Java das Substitutionsprinzip bei Ein- und Rückgabe?
Auf jeden Fall. Java ist bei den Eingabeparametern allerdings noch strenger als nötig. Eine Methode wird nur dann wirklich überschrieben, wenn die Methoden-Parameter in der Subklasse exakt mit denen in der Oberklasse übereinstimmen. Bei Kontravarianz liegt für Java eine unterschiedliche Signatur vor, weshalb die Methode nur überladen wird.