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.

 

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?