Java Tutorial #32

Typumwandlung (Casting) in Java

2019-06-10 | credit: ©Olivier/ stock.adobe

Thema in Kurzform

Wer Polymorphie in Java sinnvoll einsetzen will, kommt um das Thema Typumwandlung nicht herum. Wir bringen Ihnen in diesem Kapitel das Wichtigste dazu bei. Außerdem erfahren Sie, was genau hier eigentlich umgewandelt werden soll.

Das Problem

Wie wir im letzten Kapitel gesehen haben, können wir auf Objektreferenzen nur die Methoden ausführen, die im Referenztyp auch vorhanden sind. 

Nochmal ein kleines Beispiel hierzu:

class Hund {
    public void bellen(){}
}

class Husky extends Hund {
    public void schlittenZiehen(){}
}

Nun erzeugen wir ein neues Husky-Objekt und weisen es einer Referenz vom Typ Hund zu. Anschließend wollen wir auf der Hund-Referenz die Methode schlittenZiehen() aufrufen, denn ein Husky sollte doch eigentlich einen Schlitten ziehen können, oder? 😉

Hund h = new Husky();
h.schlittenZiehen(); // ERROR

Nein, es funktioniert nicht! Der Code erzeugt einen Fehler, da die Methode schlittenZiehen() nicht ausgeführt werden kann: cannot resolve method 'schlittenZiehen().

Auch wenn wir eindeutig wissen, dass h auf ein Objekt vom Typ Husky zeigt, interessiert das den Compiler aber nicht die Bohne! Er prüft lediglich den Referenztyp und lässt nur diejenigen Methoden ausführen, die dort auch tatsächlich vorhanden sind. Da der Referenztyp von h nun eben Hund ist und dort keine Methode schlittenZiehen() definiert ist, kann sie auch nicht ausgeführt werden - ganz egal, auf welchen Objekttyp gezeigt wird.

Dies zu wissen wird besonders bei der Arbeit mit Sammlungsklassen (z.B. ArrayList) wichtig:

Husky h1 = new Husky();
Schaeferhund h2 = new Schaeferhund();
Mops h3 = new Mops();

ArrayList<Hund> hunde = new ArrayList<>();
hunde.add(h1);
hunde.add(h2);
hunde.add(h3);

hunde.get(0).schlittenZiehen(); //  error
hunde.get(1).bewachen(); //  error
hunde.get(2).mopsMethode(); //  error

Wir erstellen zunächst drei Objekte von Hund-Subtypen und adden sie in eine ArrayList<Hund>. Anschließend rufen wir die drei Elemente der Sammlung mittels get auf und wollen die speziellen Methoden von HuskySchaeferhund und Mops ausführen. 

Dies funktioniert aber nicht! Der Grund ist, dass aus einer ArrayList<Hund> ausschließlich Hund-Referenzen herauskommen - und da die Klasse Hund eben keine Methoden schlittenZiehen()bewachen() und mopsMethode() hat, crasht der Code.

Die Lösung: Typumwandlung

Um also die Methoden eines Subtyps ausführen zu können, müssen wir die Referenz in eben diesen Subtyp "nach unten" umwandeln. Dieser Vorrang wird Typumwandlung bzw. Casting (genauer: Downcasting) genannt. 

Die Typumwandlung nach "unten" kann nicht automatisch geschehen, sondern erfolgt durch den Programmierer explizit

Was wir hierzu benötigen, ist der Cast-Operator. Er besteht aus zwei runden Klammern und darin der Name des Zieltyps. Zum Beispiel:

Hund h = new Husky();
Husky hx = (Husky) h;

Wir haben nun eine neue Referenz hx vom Typ Husky, dass auf das Husky-Objekt verweist. Durch hx haben wir jetzt Zugriff auf die Methoden der Klasse Husky erhalten! 

Auf das Objekt selbst hat dies keinerlei Auswirkung - es geht um die Referenzen! Deshalb ist es auch nicht richtig, wenn gelegentlich von "Objekt-Umwandlung" die Rede ist.

Infografik Java Casten

h.schlittenZiehen(); // ☠ error!
hx.schlittenZiehen(); // ok!

Regeln fürs Casten

Allerdings dürfen wir nicht wild durch die Gegend casten - es gibt eine strenge Regel: 

  • Es dürfen nur Typen gecasted werden, die eine Vererbungsbeziehung zueinander haben. 

Sehen wir uns folgende Vererbungshierarchie an, um das besser zu verstehen:

Infografik Java if else Verzweigung

Keine Beziehung zueinander haben die drei Subtypen HuskySchaeferhund und Mops. Wir können einen Husky deshalb niemals zu einem Schaeferhund oder Mops machen:

Husky h = new Husky();
Schaeferhund hx = (Schaeferhund) h; // error!

Dieser Code lässt sich nicht compilieren (inconvertible types). Die Husky-Referenz h kann ja niemals auf ein Schaeferhund-Objekt zeigen (sondern nur auf den Objekttyp Husky und alle Unterklassen davon). Das weiß der Compiler und blockt den Casting-Versuch sofort ab.

ClassCastException

Ein wenig anders sieht es hier aus:

Hund h = new Husky();
Schaeferhund h2 = (Schaeferhund) h;

Diesen Code lässt der Compiler durchgehen - ja er muss ihn durchgehen lassen, da er zum Zeitpunkt des Compilierens nicht wissen kann, welches Objekt der Hund-Referenz h zugewiesen wird (wir erinnern uns: Objekte werden erst zur Laufzeit erstellt). 

Aber: Da h in unserem Beispiel auf ein Objekt vom Typ Husky zeigt, wird die Typumwandlung zur Laufzeit scheitern (ClassCastException) und das Programm wahrscheinlich abstürzen. Hätten wir es mit einem Schaeferhund-Objekt zu tun gehabt, wäre das Downcasting in Ordnung gewesen.

Upcasting

Das "nach oben" Umwandeln muss nicht explizit geschrieben werden:

Husky h = new Husky();
Hund hx = (Hund) h; // nicht notwendig, schlechter Stil

Das Husky-Objekt wird in der zweiten Zeile durch Upcasting einer Hund-Variable zugewiesen (der höhere Typ). Dieser Vorgang ist aber redundant und sollte vermieden werden. Es geht doch viel einfacher: 

Husky h = new Husky();
Hund hx = h; // Polymorphie!

Da Java den höheren Typ automatisch casted und wir nichts weiter tun müssen, spricht man auch vom impliziten Casten. Wir dürfen aber nicht vergessen, dass auf hx die Husky-typischen Methoden nicht verfügbar sind.

Der instanceof-Operator

Wenn wir eine Typumwandlung vor einer ClassCastException absichern wollen, können wir vor dem Casten prüfen, ob ein gegebenes Objekt von einem bestimmten Typ ist. 

Der Test liefert zur Laufzeit true, wenn die Referenz auf eine Instanz der angegebenen Klasse zeigt. 

Hier ein konkreten Beispiel:

Hund h = new Husky();

if(h instanceof Schaeferhund){
    Schaeferhund h2 = (Schaeferhund) h;
}

Mit diesem Code kommen wir elegant um einen Crash zur Laufzeit herum. Da nämlich h eben keine Instanz von Schaeferhund ist (d.h. die Referenz h verweist nicht auf ein Schaeferhund-Objekt), wird die Typumwandlung nicht ausgeführt. 

Beachten Sie aber, dass folgende Prüfung ebenfalls true ergibt:

h instanceof Hund // true

instanceof kann nämlich nicht nur den tatsächlichen Typ eines Objekts prüfen, sondern auch alle Supertypen davon. Da h auf ein Husky-Objekt zeigt - und Hund ein Supertyp davon ist - ergibt der Test true. 

Entsprechend ist folgender Prüfung immer wahr:

beliebigeReferenz instanceof Object

Wir erinnern uns: Object ist die Superklassen aller Objekte in Java.

Werbung

Java lernen

Werde zum Java Profi!

PHP Lernen

Lerne serverbasierte Programmierung

JavaScript lernen

Skille dein Webcoding

FALCONBYTE.NET

Handmade with 🖤️

© 2018-2023 Stefan E. Heller

Impressum | Datenschutz | Changelog

Falconbyte Youtube Falconbyte GitHub facebook programmieren lernen twitter programmieren lernen discord programmieren lernen