Casts

Wie kann ich den Typ X auf Y casten?

Da das Wesen eines Casts oft missverstanden wird, hier eine Erläuterung dazu, was ein Cast eigentlich ist, wobei ich Primitivtypen außen vor lasse.

Einen Cast kann man sich als eine Zusicherung an den Compiler vorstellen, dass ein Ausdruck eines Referenztyps (meist eine Variable) zur Laufzeit ein Objekt eines bestimmten Typs referenzieren wird. Ein einfaches Beispiel:

public class Test {
    public static void main(String[] args) {
        Person[] persons = new Person[4];
        for (int i = 0; i < persons.length; i++) {
            persons[i] = new Student(i);
        }
        for (int i = 0; i < persons.length; i++) {
            System.out.println(persons[i].getMatrikelNr());
        }
    }
}

abstract class Person {
    /* ... */
}

class Professor extends Person {
    /* ... */
}

class Student extends Person {

    private int matrikelNr;

    public Student(int matrikelNr) {
        this.matrikelNr = matrikelNr;
    }

    int getMatrikelNr() {
        return matrikelNr;
    }
}

Hier meldet der Compiler einen Fehler bzgl. des Methodenaufrufs

    persons[i].getMatrikelNr()

nämlich: „The method getMatrikelNr() is undefined for the type Person“. Das liegt daran, dass für den Compiler die Elemente des Arrays persons vom Typ Person sind. Und für diesen Typ gibt es tatsächlich keine Methode getMatrikelNr(). Dass der Array zur Laufzeit Student-Exemplare enthalten wird, kann der Compiler nicht wissen. Wir können es ihm aber zusichern, nämlich durch einen Cast. Der Aufruf sähe dann so aus:

    ((Student) persons[i]).getMatrikelNr();

Hiermit versichern wir dem Compiler, dass persons[i] zur Laufzeit eine Referenz auf ein Objekt vom Typ Student enthalten wird und der Compiler erlaubt nun die Verwendung der Student-Methode. Eine Änderung des Typs eines Objekts erfolgt dabei nicht und kann auch nicht erfolgen: Ein Objekt behält für sein ganzes Leben seinen Typ, mit dem es per „new“ erzeugt wurde.

Wem die Vorstellung von „Zusicherungen“ an den Compiler nicht gefällt, der kann sich vielleicht mit der folgenden eher formalen Sichtweise anfreunden:

Der Compiler zieht für seine Typprüfungen ausschließlich die Deklarationstypen der beteiligten Ausdrücke heran. Ein Cast nun bildet zusammen mit dem Ausdruck, vor dem er steht, einen neuen Ausdruck, dessen Deklarationstyp genau der Typ ist, auf den gecastet wird. Dazu ein Beispiel:

    Person p = new Student(123456);
    Student s = (Student) p;

Hier ist p ein Ausdruck, dessen Deklarationstyp der Typ Person ist. Der Ausdruck (Student) p hingegen hingegen hat definitionsgemäß den Deklarationstyp Student, weswegen der Compiler die Zuweisung an die Variable s zulässt.

Der Compiler lässt mich eine Methode nicht aufrufen, obwohl das Objekt diese hat! Warum nicht?

Beispielcode:

    public class Test {
        public static void main(String[] args) {
            Super sup = new Sub();
            sup.m();
        }
    }
       
    class Super {
    }
   
        class Sub extends Super {
        void m() {
            System.out.println("Hello");
        }
    }

Antwort:    Der Compilerfehler in der Zeile

    sup.m();

entsteht, weil versucht wird, auf einem Ausdruck vom Deklarationstyp Super eine Methode aufzurufen, die für diesen Typ nicht definiert ist („The method m() is undefined for the type Super“). Dass die Variable sup vielleicht zur Laufzeit ein Sub enthalten wird, weiß der Compiler nicht, er prüft lediglich die Zulässigkeit der Zuweisung

    Super sup = new Sub();

anhand der Deklarationtypen links und rechts des Zuweisungsoperators. Mit einem Cast

    ((Sub) sup).m();

ändert sich der Deklarationstyp des Ausdrucks, auf dem die Methode aufgerufen wird zu Sub und der Compiler erlaubt den Aufruf. Man kann sich das auch so vorstellen, dass man dem Compiler durch den Cast zusichert, dass sup zur Laufzeit ein Sub-Exemplar referenzieren wird. Eine solche Zusicherung hebelt natürlich letztlich die Typprüfung durch den Compiler aus: Sollte sich zur Laufzeit dann herausstellen, dass sup gar kein Sub referenziert, kommt es zu einer ClassCastException.