9. Polymorphie

9. Polymorphie

Der Begriff Polymorphie

Polymorphie nach Wikipedia: (gr. πολυμορφία Polymorphia) Vielgestaltigkeit

Durch das Konzept der Vererbung sollen Anwendungen mit Objekten von Unterklassen ohne explizites Wissen über die Unterklassen umgehen können.

Objekte von Unterklassen können sich aber anders verhalten wenn die Methoden der Oberklasse in den entsprechenden Unterklassen überschrieben und neu implementiert wurden. Objekte einer Klassenhierarchie können daher abhängig von der Unterklasse verschieden Methodenimplementierungen verwenden und sich polymorph (Vielgestaltig) verhalten.

Das "Liskov Substitution Principle"

Barbara Liskov führte 1987 das nach ihr benannte Prinzip welches sich mit der Substitution (Ersetzbarkeit) in der objektorientierten Programmierung beschäftigt:

Wenn eine Klasse S eine Unterklasse(Subtyp) von T ist, können alle Instanzen von T (Oberklasse) durch Instanzen von S (Unterklasse) ersetzt werden ohne dass sich das Verhalten des Programmes ändert.

 Eng mit diesem Prinzip ist auch Betrand Meyer's in der Programmiersprache Eiffel umgesetztes "Design by contract" verwandt.

Stefan Schneider Mon, 08/23/2010 - 19:57

9.1 Polymorphes Verhalten bei Vererbung und Erweiterung

9.1 Polymorphes Verhalten bei Vererbung und Erweiterung

 Der Aspekt der Erweiterung bei der Vererbung ermöglicht problemlos polymorphes Verhalten von Objekten.

Im unten gezeigten Beispiel wird eine Klassenhierarchie verwendet in der in jeder Unterklasse ein Attribut und eine Methode hinzugefügt wird

Ein Programm welches nur Instanzen der Klasse Kraftwagen kennt hat keine Probleme Instanzen von LastKraftwagen oder Bus zu verwenden. Die Instanzen besitzen alle ein Attribut nummernschild und eine Methode getKennZeichen().

Polymorphismus funktioniert in Java nur bei Attributen und Methoden die von einer gemeinsamen Basisklasse geerbt worden sind!

Klassen mit namensgleichen Attributen und Methoden profitieren nicht vom Polymorphismus. Der Übersetzer kontrolliert die Klassenhierarchie beim Übersetzen und unterbindet Zuweisungen zwischen Typen(Klassen) die namensgleiche Methoden besitzen, diese aber nicht von einer gemeinsamen Klasse erben.

Dieses Konzept lässt sich in Java wie folgt umsetzen:

import Kraftwagen;
import LastKraftwagen;
import Bus;
...
Kraftwagen[] flotte = new Kraftwagen[3];

flotte[0] = new Kraftwagen();
flotte[1] = new LastKraftwagen();
flotte[2] = new Bus();
...
for (i=0; i<3; i++) {
   // Das Programm kennt im Moment nur Kraftwagen!
   System.out.println(flotte[i].getKennZeichen());
   }
...

Das Programm kann innerhalb der for-Schleife mit Instanzen der Klasse LastKraftwagen und Bus umgehen. Es muss sich aber auf die in der Klasse Kraftwagen bekannten Eigenschaften des Objekts beschränken.

Stefan Schneider Wed, 10/13/2010 - 17:11

Anonymous (not verified)

Fri, 12/04/2015 - 14:15

Haben Sie im letzten Satz: "Das Programm kann mit Instanzen der Klasse LastKraftwagen und Bus umgehen da es sich auf die in der Klasse Kraftwagen bekannten Eigenschaften des Objekts beschränken muss."

das Wort "nicht" vergessen?

Müsste der Satz nicht:"Das Programm kann NICHT mit Instanzen der Klasse LastKraftwagen und Bus umgehen da es sich auf die in der Klasse Kraftwagen bekannten Eigenschaften des Objekts beschränken muss." heißen?

Stefan Schneider

Fri, 12/04/2015 - 15:26

In reply to by Anonymous (not verified)

Der Satz war schlecht formuliert. Ich habe ihn neu formuliert. Ich hoffe, dass ist jetzt besser verständlich.

9.2 Polymorphie beim Überschreiben

9.2 Polymorphie beim Überschreiben

Java erlaubt es Methoden von Oberklassen in Unterklassen zu verdecken und neu zu implementieren. Hierdurch können Methoden an die Anforderungen einer spezialisierten Klasse angepasst werden. Man spricht hier vom Überschreiben von Methoden wenn die folgende Definition gilt:

Definition
Überschreiben einer Methode
Bei einer überschriebenen Methode müssen die Signatur und der Rückgabewert der überschriebenen Methode identisch mit der Signatur und dem Rückgabewert der Methode einer Oberklasse sein.

Beim Überschreiben von Methoden und Attributen ist das "Liskov Substitution Principle" nicht mehr automatisch garantiert. Hier wir die überschreibende Methode der Unterklasse an die Stelle der überschriebenen Methode der Oberklasse bei allen Instanzen der Unterklasse verwendet. Es wird ein anderer Code ausgeführt. Dieser Code könnte theoretisch auch zu einem anderen Verhalten führen.

Anbei das Beispiel einer Klasse Point, die einen zweidimensionalen Punkt modelliert und der Klasse CircleIsPoint die von Punkt erbt und zusätzlich ein Attribut zur Verwaltung des Radius hat:

Klasse Point

public class Point {
    private double x;
    private double y;

    public Point(double xx, double yy) {
        x = xx;
        y = yy;
    }
    public void setXY(double xx, double yy) {
        x = xx;
        y = yy;
    }
    public double getX() { return x;}
    public double getY() { return y;}

    public void print() {System.out.println(toString());}
    public String toString() {return ("x: " + x + " y: " + y);}
}

Klasse CircleIsPoint

public class CircleIsPoint extends Point{
    private double radius;

    public CircleIsPoint(double xx, double yy, double r) {
        super(xx,yy);
        radius=r;
    }
    public double getRadius() {return radius;}
    public void setRadius(double r) {radius=r;}

    public void print() { //Uberschriebene Methode
        System.out.println(super.toString() + " radius: " + radius);
    }
}

Ein Hauptprogramm

public class Main {
    public static void main(String[] args) {
        Point[] pf = new Point[3];
        pf[0] = new Point (2.2, 3.3);
        pf[1] = new Point (7.7, 8.8);
        pf[2] = new CircleIsPoint(4.4,5.5,6.6);

        pf[0].print();
        pf[1].print();
        pf[2].print();
    }
}

Bei der Ausführung des Programms ergibt sich die folgende Objekstruktur:

Die Ausgabe ist die folgende:

x: 2.2 y: 3.3
x: 7.7 y: 8.8
x: 4.4 y: 5.5 radius: 6.6

Die überschriebene print() Methode erzeugt eine sinnvolle Ausgabe, die zusätzlich den Radius ausgibt ohne das das komsumierende Hauptprogramm "weiß", das es sich um eine überschriebene Methode handelt.

Da der Begriff einer "sinnvollen" Erweiterung nicht von Werkzeugen abgeprüft werden kann, ergibt sich ein Risiko des Missbrauchs. Ein Beispiel ist die folgende Implementierung der Methode print() in der Klasse CircleIsPoint().

public class CircleIsPoint extends Point{
    ...
    public void print() {
        System.out.println("Methode print() muss noch implementiert werden...");
    }
}

Der Unterschied zwischen der recht menschlichen Implementierung und der Implementierung die auch den Radius ausgibt ist nur gering.

Es gibt im schlimmsten Fall Programme, die diesen Text lesen und weder die zusätzlichen Information über den Radius interpretieren können, noch mit einem anderen Text umgehen können.

Ist eine Klasse und/oder die Methode nicht als final deklariert, muss ein Konsument dieser Klasse mit einem Überschreiben von Methoden rechnen!

Hiermit ergibt sich für den Polymorphismus beim Überschreiben die folgende Abschätzung:

Vorteil Nachteil
Kostensenkung Entwicklung: Existierender Code kann unmodifiziert mit neuen Unterklassen umgehen Kostensteigerung QA und Service: Der Polymorphismus kann durch Missbrauch zu unerwarteten Effekten führen, die erst zur Laufzeit auftreten.

Begriffsbestimmung: Überschriebene und überladene Methoden

Es gibt in Java überschriebene (englisch overriding) und überladene (englisch overloading) Methoden. Beide Begriffe werden leicht verwechselt!

  Überschriebene Methode Überladene Methode
Methodenname identisch identisch
Eingabeparameterliste identisch (Typen, Anzahl, Folge) unterschiedlich!
Rückgabewert identisch nicht relevant
Klassenbeziehung der Methoden Methoden gehören zu unterschiedlichen Klassen die aber in einer Ober- Unterklassebeziehung stehen Methoden gehören zur gleichen Klasse

Zur vollständigen Begriffsverwirrung: Methoden können andere Methoden der gleichen Klasse überladen während sie eine Methode der Oberklasse überschreiben.

Siehe die Methode mult() im folgenden Beispiel:

Überschriebene und überladene Methoden 

Stefan Schneider Wed, 10/13/2010 - 17:12

Anonymous (not verified)

Mon, 12/01/2014 - 01:40

Kann es sein dass hier ein Fehler vorliegt?
"Hier tritt nun die überschreibende Methode an die Stelle der überschriebenen Methode."

9.3 Casting: Typkompabilität bei Zuweisungen

9.3 Casting: Typkompabilität bei Zuweisungen

Bisher wurden bereits implizit das Konzept von Referenzvariablen verwendet, die wechselseitig auf Objekte ihres Typs (Klasse) oder auf Objekte von Unterklassen gezeigt haben.

Java ist trotz des Polymorphismus eine streng typisierte Sprache. Das bedeutet:

Strenge Typisierung
Der Übersetzer erlaubt schon beim Übersetzen nur typsichere Zuweisungen und Operationen (und meldet unsichere Operationen als Fehler)

Dieses Konzept steigert die Qualität der ausgelieferten Anwendungen. Fehler aufgrund inkorrekter Typen können beim Anwender zur Laufzeit nicht mehr auftreten. Der Übersetzer zwingt den Entwickler nur typsichere Operationen durchzuführen. Eine typsichere Operation ist zum Beispiel das Aufrufen einer Methode von der man sicher weiß, das sie für eine gegebene Sprache existiert.

"Casting" in der Programmierung

engl. Casting hier: Formen, gießen, krümmen

Das implizite oder explizite Ändern des Typs eines Objekts oder Datenstruktur bei einer Zuweisung oder der Methodenauswahl

Der "Upcast"

Ein "Upcast"  ist eine Typkonversionen einer Instanz einer Unterklasse auf den Typ einer Oberklasse. Sie wurden bisher implizit in den vorhergehenden Beispielen verwendet, wie zum Beispiel bei der Klasse CircleIsPoint die aus der Klasse Point abgeleitet wurde:

import Point;
import CircleIsPoint;
...
 Point p1         = new Point (2.2, 3.3);
 CircleIsPoint c1 = new CircleIsPoint(4.4,5.5,6.6);

Point p2 = c1; //Das explizite Wissen über den Objekttyp CircleIsPoint geht verloren
...
p1.print();
p2.print();

Die Zuweisung von c1 auf p1 ist

  • sicher da sich alle Instanzen der Klasse CircleIsPoint wie Instanzen der Klasse Point verhalten
  • vom Javaübersetzer erlaubt da sicher!
Upcast
Der "Upcast" ist eine sichere Typkonversation da spezialisierte Klasseninstanzen auf Referenzen von allgemeineren Klassen zugewiesen werden.

 Der "Downcast" oder unsichere "Cast"

Das umgekehrte Prinzip gilt jedoch nicht.

Eine Instanz einer allgemeineren Klasse hat nicht alle Eigenschaften einer spezialisierten Klasse. Das folgende Beispiel wird nicht vom Javaübersetzer akzeptiert:

import Point;
import CircleIsPoint;
...
 Point p1         = new Point (2.2, 3.3);
 CircleIsPoint c1 = new CircleIsPoint(4.4,5.5,6.6);

CircleIsPoint c2 = p1;
...
double r1 = c1.getRadius();
double r2 = c2.getRadius();

Eine Instanz vom Typ Point hat nicht die Eigenschaften einer Instanz vom Type CircleIsPoint. Im UML Diagramm sieht der obige Versuch wie folgt aus:

 

Der Übersetzer erzeugt schon beim Versuch der Zuweisung eine Fehlermeldung:

Main.java:30: incompatible types
found   : l9vererbungassoziation.Point
required: l9vererbungassoziation.CircleIsPoint
CircleIsPoint c2 = p1;
1 error

Es gibt jedoch auch Fälle in denen eine Typkonversion erzwungen werden muss. Im verwendeten Beispiel ist dies der Fall wenn man einen Kreis in einem Feld von Point verwaltet. Wenn man die Referenz benutzt um den Radius auszulesen ergibt sich folgendes Problem wenn man zum Auslesen den Typ CircleIsPoint wie folgt verwendet:

public static void main(String[] args) {
        Point[] pf = new Point[3];
        pf[0] = new Point (2.2, 3.3);
        pf[1]  = new Point (2.22, 3.33);
        pf[2] = new CircleIsPoint(4.4,5.5,6.6);

        pf[0].print();
        pf[1].print();
        pf[2].print();

        CircleIsPoint cip1 = pf[2];
        double r2 = cip1.getRadius();
    }

Der Übersetzer meldet den folgenden Fehler da die beiden Typen keine sichere Typkonversion zulassen:

Main.java:24: incompatible types
found   : l9vererbungassoziation.Point
required: l9vererbungassoziation.CircleIsPoint
        CircleIsPoint cip1 = pf[2];
1 error

Dieses Problem kann man versuchen zu Lösen in dem man die Variable cip1 mit dem Typ Point versieht:

public static void main(String[] args) {
        Point[] pf = new Point[3];
        pf[0] = new Point (2.2, 3.3);
        pf[1]  = new Point (2.22, 3.33);
        pf[2] = new CircleIsPoint(4.4,5.5,6.6);

        pf[0].print();
        pf[1].print();
        pf[2].print();

        Point cip1 = pf[2];
        double r2 = cip1.getRadius();
}

Dies Maßnahme erlaubt das Auslesen der Feldvariable. Sie scheitert jedoch eine Zeile weiter beim Aufruf der Methode getRadius(). Die Klasse Point kennt keine Methode getRadius()...

Main.java:25: cannot find symbol
symbol  : method getRadius()
location: class l9vererbungassoziation.Point
        double r2 = cip1.getRadius();
1 error

Die Lösung besteht in einer expliziten Typkonversion (" cast")  mit Hilfe einer runden Klammer und Angabe des Typs:

public static void main(String[] args) {
        Point[] pf = new Point[3];
        pf[0] = new Point (2.2, 3.3);
        pf[1] = new Point (2.22, 3.33);
        pf[2] = new CircleIsPoint(4.4,5.5,6.6);

        pf[0].print();
        pf[1].print();
        pf[2].print();

        CircleIsPoint cip1 = (CircleIsPoint)pf[2];
        double r2 = cip1.getRadius();
    }

Mit dieser Typkonversion zwingt der Entwickler den Übersetzer zur Zuweisung. Der Übersetzer kann sich dieses Wissen nicht von alleine herleiten. Die Verantwortung liegt jetzt beim Entwickler. Würde der Entwickler den folgenden Cast erzwingen, würde das Programm auch fehlerfrei übersetzen:

CircleIsPoint cip1 = (CircleIsPoint)pf[1];
double r2 = cip1.getRadius();

Beim Aufruf der Methode getRadius() würde jetzt jedoch zur Laufzeit eine Ausnahme (Exception) geworfen werden. Der Entiwckler hat in diesem Fall den Übersetzer zu einem inkorrekten Übersetzungsvorgang gezwungen.

Der Fehler tritt jetzt erst zur Laufzeit auf. Dies kann für den Entwickler sehr teuer werden, da die Software den Fehler eventuell erst beim Kunden zeigt.

Zusammenfassung explizite Typkonversion (Downcast)

Die Syntax 

KlasseC variable2;
...
KlasseA variable1 = (KlasseB) variable2;

erlaubt die Zuweisung einer Objektreferenz variable2 auf eine Objektreferenz variable1 solange der Typ KlasseB in der Klammer eine sichere Konvertierung von KlasseB auf KlasseA erlaubt. Diese Zuweisung funktioniert unabhängig vom tatsächlichen Typ (KlasseC) von variable2.

Explizite Typkonversionen sind in manchen Fällen aufgrund des Polymorphismus notwendig. Sie haben jedoch eine Reihe von Konsequenzen:

  • Der Entwickler übernimmt explizit die Verantwortung für eine korrekte Konversion. Diese hat normalerweise der Übersetzer!
  • Fehler werden nicht mehr zum Übersetzungszeitpunkt erkannt.
  • Fehler werden erst zur Laufzeit erkannt. Sie führen zur Ausnahmebehandlung zur Laufzeit. Die Anwendung beendet sich falls diese Ausnahmen nicht gefangen und behandelt werden.

Downcasts sind problematische, explizite Typkonvertierungen.

Ihre Verwendung sollte wenn möglich vermieden werden, da bestimmte Fehlerklassen zum Übersetzungszeitpunkt nicht geprüft werden können.

 

Stefan Schneider Wed, 10/13/2010 - 17:21

Anonymous (not verified)

Wed, 11/13/2019 - 09:28

Der "Upcast"
Sie wurden bisher implizit in den vorhergehenden Beispielen verwendet, wie zum Beispiel bei der Klasse CircleIsPoint die aus der Klasse Point abgeleitet wurde:

CircleIsPoint = Point ist genau aber ein Downcast, obiger Satz ist vielleicht nicht optimal.

---------
Die Zuweisung von c1 auf p1 ist

Ist hier vielleicht die Zuweisung von c1 auf p2 gemeint?

Stefan Schneider

Wed, 11/13/2019 - 14:21

In reply to by Anonymous (not verified)

Ich verstehe Ihre Frage nicht so 100%...

  • Ein Upcast ist eine Zuweisung von eines Objekts einer speziellen Klasse auf eine allgemeinere Klasse. Diese Zuweisung ist sicher es geht aber Wissen verloren. Eben das Wissen welches zur spezielleren Klasse gehört
  • Eine Downcast ist prinzipiell unsicher. Java kann nicht wissen ob sich hinter dem Zeiger auf ein allgemeines Objekt, ein Objekt versteckt welches die Eigenschaften der speziellen Klasse hat.

Macht das Sinn?

9.4 Sonderfall: überschriebene Attribute

9.4 Sonderfall: überschriebene Attribute

Der Polymorphismus funktioniert in Java nicht im Fall von überschriebenen Attributen die einen unterschiedlichen Typ besitzen.

Bei überschriebenen Attributen wendet Java den Typ der Referenz an um das Attribut zu bestimmen.

Im Beispiel der Klassen Point und CircleIsPoint wird dies mit Hilfe des Attributs layer gezeigt:

Klasse Point

public class Point {
    ...
    public double layer = 5D;
    ...
    public void print() {System.out.println(toString());}
    public String toString() {return ("x: " + x + " y: " + y);}
}

Klasse CircleIsPoint

public class CircleIsPoint extends Point{
    ...
    public int layer = 1;
    ...
    public void print() {
        System.out.println(super.toString() + " radius: " + radius);
    }
}

Hauptprogramm

public class Main {
    public static void main(String[] args) {
        Point[] pf = new Point[3];
        pf[0] = new Point (2.2, 3.3);
        pf[1] = new Point (2.22, 3.33);
        pf[2] = new CircleIsPoint(4.4,5.5,6.6);

        pf[0].print();
        pf[1].print();
        pf[2].print();

        Point p2 = pf[2];
        p2.print(); // toString von CircleIsPoint wird benutzt
        double t1 = 1000.0 + p2.layer; // layer von Point wird benutzt
        System.out.println(t1);

        CircleIsPoint cip = (CircleIsPoint)pf[2];
        cip.print(); // toString von CircleIsPoint wird benutzt
        double t2 = 1000.0 + cip.layer; // price von CircleIsPoint wird benutzt
        System.out.println(t2);
    }
}

Hiermit ergeben sich die folgenden Belegungen:

Konsolenausgabe

x: 2.2 y: 3.3
x: 2.22 y: 3.33
x: 4.4 y: 5.5 radius: 6.6
x: 4.4 y: 5.5 radius: 6.6
1005.0
x: 4.4 y: 5.5 radius: 6.6
1001.0

Im ersten Fall (p2) wird das Attribut layer vom Typ double benutzt. Im Fall der Variable cip vom Typ CircleIsPoint wird die Variable layer vom Typ int benutzt.

Es kommt kein Polymorphismus zum Tragen, die Auswahl des Attributs richtet sich nach dem Typ der Referenz!

Tipp: Durch das strikte Anwenden von Datenkapselung greift man nur noch über Zugriffsmethoden auf Attribute zu. Hierdurch wird der Polymorphismus auch beim Zugriff auf Attribute (intuitiv) gewährleistet. Das Ausnutzen des nicht polymorphen Verhalten betrifft dann nur noch Sonderfälle in denen man dies explizit wünscht.

Stefan Schneider Wed, 10/13/2010 - 17:14

Anonymous (not verified)

Sat, 12/14/2019 - 14:27

Warum wird im Hauptprogramm durch p2.print(); die Methode der Klasse CircleIsPoint aufgerufen und nicht die Methode von Point?

Stefan Schneider

Sun, 12/15/2019 - 10:17

In reply to by Anonymous (not verified)

p2. print() im Hauptprogramm ruft print() von Punkt auf. Hier ist dann die Frage welches Attribut ausgelesen wird. cip.print() ruft die print() Methode aus CircleIsPoint auf. Hier muss mann dann auch überlegen welches Attribut ausgelesen wird.

Anonymous (not verified)

Sat, 12/14/2019 - 14:34

Die Namen und Werte im Diagramm passen nicht zum Code. Daher wird das Beispiel nicht klar.

Stefan Schneider

Sun, 12/15/2019 - 10:12

In reply to by Anonymous (not verified)

Ich habe hier ein Diagramm aus einem anderen Beispiel verwendet. Ich muss überlegen ob ich es lösche oder anpasse.

9.5 instanceof-Operator

9.5 instanceof-Operator

Normalerweise sind zur Laufzeit eines Programmes bereits alle Typen bekannt und wurden vom Übersetzer schon beim Übersetzen des Programms kontrolliert.

Durch die Möglichkeit der Typkonvertierung durch "casten", und "Upcasting" kann man die genaue Information über einen Typ einer Instanz zur Laufzeit verlieren. Das bedeutet, sie ist im Kontext einer Methode oder Klasse nicht mehr bekannt.

Zur Bestimmung eines Typs zur Laufzeit bietet Java den instanceof Operator an mit dem man Instanzen auf Ihren Typ prüfen kann.

Im vorhergehenden Beispiel mit den Klassen Point und CircleIsPoint kann man den instanceof Operator verwenden um einen Laufzeitfehler bei einer cast-Operation zu vermeiden:

public class Main {

    public static void main(String[] args) {
        Point[] pf = new Point[3];
        pf[0] = new Point (2.2, 3.3);
        pf[1]  = new Point (2.22, 3.33);
        pf[2] = new CircleIsPoint(4.4,5.5,6.6);

        pf[0].print();
        pf[1].print();
        pf[2].print();

        double r2 = 0;
        CircleIsPoint cip1;
        if (pf[2] instanceof CircleIsPoint) {
            cip1 = (CircleIsPoint)pf[2];
            r2 = cip1.getRadius();
            }
    }
}

Weiterführend: getClass() Methode

Die Javalaufzeitumgebung verfügt über ein vollständiges, dynamisches System zur Verwaltung von Metainformationen inklusive der Informationen über eine Klasse.

Hierzu dient die Klasse Class die man über die Methode Object.getClass() benutzen kann. Die Methode getClass() wird an alle Klassen vererbt. Hiermit kann man Informationen über die Klasse selbst, wie zum Beispiel ihren Name und die Oberklasse auslesen:

public class Main {

    public static void main(String[] args) {
        Point[] pf = new Point[3];
        pf[0] = new Point (2.2, 3.3);
        pf[1]  = new Point (2.22, 3.33);
        pf[2] = new CircleIsPoint(4.4,5.5,6.6);

        pf[0].print();
        pf[1].print();
        pf[2].print();

        double r2 = 0;
        CircleIsPoint cip1;
        Class myClass = pf[2].getClass();
        Class mySuperClass = myClass.getSuperclass();
        System.out.println("pf[2] Klassenname: " + myClass.getSimpleName());
        System.out.println("pf[2] Oberklasse: "  + mySuperClass.getSimpleName());
        if (pf[2] instanceof CircleIsPoint) {
            cip1 = (CircleIsPoint)pf[2];
            r2 = cip1.getRadius();
            }
    }
}

Die Konsolenausgabe des Programmes ist die Folgende:

x: 2.2 y: 3.3
x: 2.22 y: 3.33
x: 4.4 y: 5.5 radius: 6.6
pf[2] Klassenname: CircleIsPoint
pf[2] Oberklasse: Point
Stefan Schneider Wed, 10/13/2010 - 17:23

9.6 Übungen

9.6 Übungen

Duke als Boxer

9.6.1 Haustierpolymorphismus

Haustierklassen

Klasse Haustier

  • abstrakte Klasse
  • Attribute
    • privat: name
    • privat: steuerpflichtig
    • privat: jahreskostenTierarzt
  • Methoden
    • Lese und Schreibmethode für Namen
    • Lesemethode für Steuerpflicht
    • Konstruktor der Namen, Steuerpflicht und Jahreskosten Tierarzt erfasst
    • Methode beschreibung() Typ String. Gibt Text mit Namen und eventueller Steuerpflicht zurück.

Klasse Hund

  • abgeleitet aus Haustier
  • Hunde sind steuerpflichtig
  • privates Attribut: rasse
  • Methoden
    • Konstruktor der Namen, Jahreskosten Tierarzt und Rasse erfasst
    • Lesemethode für Rasse
    • Überschriebene Methode beschreibung() die zusätzlich hundespezifische Daten zurück gibt

Klasse Katze

  • abgeleitet aus Haustier
  • Katzen sind nicht steuerpflichtig
  • privates Attribut: lieblingsVogel (Referenz auf Vogel)
  • Methoden
    • Konstruktor der Namen des Katze, Jahreskosten Tierarzt und die Referenz des Lieblingsvogels erfasst
    • vogel() Ausgabe ist der Name des Vogels als Zeichenkette
    • Überschriebene Methode beschreibung() die zusätzliche katzenspezifische Daten zurück gibt.
    • setzen des Lieblingsvogelattribut

Klasse Vogel

  • abgeleitet aus Haustier
  • Vögel sind nicht steuerpflichtig
  • privates Attribut: singvogel (boolscher Wert)
  • Methoden
    • Konstruktor der Namen des Vogels, JahresKosten Tierarzt und Singvogeleigenschaft erfasst
    • Lesemethode für Singvogeleigenschaft
    • Überschriebene Methode beschreibung() die zusätzliche vogelspezifische Eigenschaften ausgibt

Klasse TierTest

Schreiben Sie ein Hauptprogramm das folgende Aufgaben ausführt:

  • Verwaltung von mindestens 2 Haustieren pro Tierart einem gemeinsamen statischen Feld vom Typ Haustier
  • Methoden
    • populate(): Anlegen von mindestens 2 Tieren pro Tierart im statischen Haustierfeld
    • neuerLieblingsvogel(): Benutzen Sie einen zweiten Vogel, der aus dem Haustierfeld geholt und weisen Sie ihn einer Katze aus dem Haustierfeld zu
      • prüfen Sie vor den Zuweisungen, dass Sie eine Katze, beziehungsweise einen Vogel aus dem Feld ausgelesen haben
    • iterate(): Itererieren über das Feld und folgende Information ausdrucken
      • Typ des Tieres
      • Inhalt von beschreibung()
      • Summerien Sie die Tierarztkosten auf und geben Sie sie am Ende aus.
  • Benutzen Sie den unten angebenen Rumpf für Ihre Implementierung
 

package s1.block9;

public class TierTest {
    private static Haustier hausTiere[];
    public static void main(String[] args) {
        populate();
        neuerLieblingsvogel();
        iterate();
    }

    public static void populate() {
        hausTiere = new Haustier[6];
        /* Implementierung */
    }

    public static void neuerLieblingsvogel() {
        /* Implementierung */
    }

    public static void iterate() {
        /* Implementierung */
    }
}
 

Optional

Versuchen Sie eine Zuweisung einer Referenz eines inkompatiblen Typen als Lieblingvogel zu einer Katze. Die Routine soll durch casten übersetzen. Zur Laufzeit soll die Zuweisungweisungsmethode der Katze den Fehlversuch auf der Konsole dokumentieren

Tipp:

Die Methode .getClass() der Klasse Object liefert eine Referenz auf die Beschreibung der Klasse. Man kann diese Referenz direkt ausdrucken. Es wird der Name der Klasse inklusive eventueller Paketzugehörigkeit ausgedruckt.

Will man nur den Klassennamen ausdrucken, muss man von einer Instanz vom Typ Class die Methode .getSimpleName()aufrufen.

Stefan Schneider Wed, 10/13/2010 - 17:26

9.7 Lösungen

9.7 Lösungen

 9.7.1 Haustierpolymorphismus

Klasse Haustier

 
package s1.block9;
public abstract class Haustier {
 
    private String name;
    private boolean steuerpflichtig;
    private double kostenTierarzt;
 
    /**
     * Get the value of kostenTierarzt
     *
     * @return the value of kostenTierarzt
     */
    public double getKostenTierarzt() {
        return kostenTierarzt;
    }
 
    /**
     * Set the value of kostenTierarzt
     *
     * @param kostenTierarzt new value of kostenTierarzt
     */
    public void setKostenTierarzt(double kostenTierarzt) {
        this.kostenTierarzt = kostenTierarzt;
    }
 
 
    /**
     * Get the value of steuerpflichtig
     *
     * @return the value of steuerpflichtig
     */
    public boolean getSteuerpflichtig() {
        return steuerpflichtig;
    }
 
    /**
     * Set the value of steuerpflichtig
     *
     * @param steuerpflichtig new value of steuerpflichtig
     */
    public void setSteuerpflichtig(boolean steuerpflichtig) {
        this.steuerpflichtig = steuerpflichtig;
    }
 
 
    /**
     * Get the value of name
     *
     * @return the value of name
     */
    public String getName() {
        return name;
    }
 
    /**
     * Set the value of name
     *
     * @param name new value of name
     */
    public void setName(String name) {
        this.name = name;
    }
 
    public Haustier(String name, boolean steuerpflichtig, double kostenTierarzt) {
        this.name = name;
        this.steuerpflichtig = steuerpflichtig;
        this.kostenTierarzt = kostenTierarzt;
    }
 
    public String beschreibung() {
        String stpf = (steuerpflichtig) ? ", " : ", nicht ";
        String b = "Name :" + name
                + stpf + "steuerpflichtig, Kosten: "
                + kostenTierarzt;
        return b;
    }
 
 
}
 

Klasse Hund

 
package s1.block9;
 
/**
 *
 * @author s@scalingbits.com
 */
public class Hund extends Haustier{
 
    private String rasse;
 
    /**
     * Get the value of rasse
     *
     * @return the value of rasse
     */
    public String getRasse() {
        return rasse;
    }
 
 
    public Hund(String name, double kostenTierarzt, String rasse) {
        super(name,true,kostenTierarzt);
        this.rasse = rasse;
 
    }
 
    public String beschreibung() {
        return super.beschreibung() + ", Rasse: " + rasse;
    }
}
 

Klasse Katze

package s1.block9;
 
public class Katze extends Haustier {
 
    private Vogel lieblingsVogel;
 
    public String vogel() {
        String vname;
        if (lieblingsVogel != null)
            vname = lieblingsVogel.getName();
        else vname = "keinen Vogel";
        return vname;
    }
/**
 * 
 * @param v setzen des Lieblingsvogel
 */
    public void setVogel(Vogel v) { lieblingsVogel=v;}
 
    public Katze(String name, double kostenTierarzt, Vogel lieblingsVogel) {
        super(name, false, kostenTierarzt);
        if ((lieblingsVogel !=null) && (lieblingsVogel instanceof Vogel))
            this.lieblingsVogel = lieblingsVogel;
 
    }
 
    public String beschreibung() {
        return super.beschreibung() + ", mag " + vogel();
    }
}
 

Klasse Vogel

package s1.block9;
 
/**
 *
 * @author s@scalingbits.com
 */
public class Vogel extends Haustier{
 
    private boolean singvogel;
 
    /**
     * Get the value of singvogel
     *
     * @return the value of singvogel
     */
    public boolean getSingvogel() {
        return singvogel;
    }
 
    public Vogel(String name, double kostenTierarzt, boolean singvogel) {
        super(name, false, kostenTierarzt);
        this.singvogel = singvogel;
    }
 
   public String beschreibung() {
        String saenger = (singvogel) ? "ein" : "kein";
        return super.beschreibung() + ", ist "
                + saenger + " Singvogel";
    }
 
}
 

Klasse TierTest

package s1.block9;
 
public class TierTest {
 
    private static Haustier hausTiere[];
 
    public static void main(String[] args) {
        populate();
        neuerLieblingsvogel();
        iterate();
    }
 
    public static void populate() {
        hausTiere = new Haustier[6];
 
        hausTiere[0] = new Vogel("Hansi", 50.55f, true);
        hausTiere[1] = new Vogel("Piep", 50.44f, false);
        hausTiere[2] = new Hund("Waldi", 222.22f, "Dackel");
        hausTiere[3] = new Hund("Fiffi", 202.22f, "Terrier");
        hausTiere[4] = new Katze("Isis", 88.88f, (Vogel) hausTiere[0]);
        hausTiere[5] = new Katze("Napoleon", 77.77f, null);
    }
 
    public static void neuerLieblingsvogel() {
        Vogel v;
        Katze k;
        if ((hausTiere[1] instanceof Vogel)
                && (hausTiere[4] instanceof Katze)) {
            v = (Vogel) hausTiere[1];
            k = (Katze) hausTiere[4];
            k.setVogel(v);
        }
    }
 
    public static void iterate() {
        double kosten = 0;
        for (int i = 0; i < hausTiere.length; i++) {
            kosten += hausTiere[i].getKostenTierarzt();
            System.out.println(
                    "Art: " + hausTiere[i].getClass().getSimpleName()
                    + "; " + hausTiere[i].beschreibung());
        }
System.out.println("Gesamtjahrekosten "+ kosten +" Euro");
    }
}

Ausgaben:

Art: Vogel; Name :Hansi, nicht steuerpflichtig, Kosten: 50.55, ist ein Singvogel
Art: Vogel; Name :Piep, nicht steuerpflichtig, Kosten: 50.44, ist kein Singvogel
Art: Hund; Name :Waldi, steuerpflichtig, Kosten: 222.22, Rasse: Dackel
Art: Hund; Name :Fiffi, steuerpflichtig, Kosten: 202.22, Rasse: Terrier
Art: Katze; Name :Isis, nicht steuerpflichtig, Kosten: 88.88, mag Piep
Art: Katze; Name :Napoleon, nicht steuerpflichtig, Kosten: 77.77, mag keinen Vogel
Gesamtjahrekosten 692.08 Euro

 

Stefan Schneider Wed, 10/13/2010 - 17:27

9.8 Lernziele

9.8 Lernziele

Am Ende dieses Blocks können Sie:

  • ... den Begriff der Polymorphie und das "Liskov Substitution" Prinzip im Javakontext erklären und anwenden
  • ... erkennen wenn zwei Javaklassen polymorphe Methoden haben
  • ... das Konzept des Überschreibens in Java erklären und anwenden
  • ... überschriebene Methoden und überladene Methoden unterscheiden (Anmerkung: Fällt auch mir immer wieder schwer...)
  • ... erkennen was erfüllt sein muss um eine Methode zu überschreiben
  • ... die Typkonversionen "Upcasts" und "Downcasts" in Java erkennen und erklären welche Zuweisungen sicher sind und welche nicht
  • ... erklären was überschriebene Attribute von überschreibenen Methoden unterscheidet
  • ... verschiedene Methoden zur Bestimmung des Typs eines Objekts nennen.

Lernzielkontrolle

Sie sind in der Lage die Fragen zum Polymorphismus  zu beantworten

Feedback

Zur Umfrage

QR Code für Umfrage

Stefan Schneider Mon, 10/08/2012 - 00:09