explizites Casting von Superklasse zu Unterklasse

161
public class Animal {
    public void eat() {}
}

public class Dog extends Animal {
    public void eat() {}

    public void main(String[] args) {
        Animal animal = new Animal();
        Dog dog = (Dog) animal;
    }
}

Die Zuweisung Dog dog = (Dog) animal;generiert keinen Kompilierungsfehler, aber zur Laufzeit generiert sie einen ClassCastException. Warum kann der Compiler diesen Fehler nicht erkennen?

Saravanan
quelle
50
SIE weisen den Compiler an, den Fehler NICHT zu erkennen.
Mauricio

Antworten:

325

Wenn Sie eine Besetzung verwenden, sagen Sie dem Compiler im Wesentlichen: "Vertrauen Sie mir. Ich bin ein Profi, ich weiß, was ich tue, und ich weiß, dass ich Ihnen nicht garantieren kann, dass diese animalVariable definitiv ist werde ein Hund sein. "

Da das Tier eigentlich kein Hund ist (es ist ein Tier, das Sie tun könnten Animal animal = new Dog();und es wäre ein Hund), löst die VM zur Laufzeit eine Ausnahme aus, weil Sie dieses Vertrauen verletzt haben (Sie haben dem Compiler gesagt, dass alles in Ordnung ist und es ist nicht!)

Der Compiler ist ein bisschen schlauer als nur blind alles zu akzeptieren. Wenn Sie versuchen, Objekte in verschiedenen Vererbungshierarchien umzuwandeln (z. B. einen Hund in einen String umwandeln), wirft der Compiler ihn zurück, weil er weiß, dass dies möglicherweise niemals funktionieren könnte.

Da Sie den Compiler im Wesentlichen nur daran hindern, sich zu beschweren, ist es bei jedem Casting wichtig zu überprüfen, ob Sie keine verursachen, ClassCastExceptionindem Sie instanceofeine if-Anweisung (oder etwas in diesem Sinne) verwenden.

Michael Berry
quelle
Vielen Dank, aber Sie müssen Hund verlängern von Tier, wenn nicht, nicht arbeiten :)
liefern
65
Ich
finde
3
@delive Natürlich können Sie tun, aber nach der Frage, Dog sich aus verlängern Animal!
Michael Berry
52

Denn theoretisch Animal animal kann ein Hund sein:

Animal animal = new Dog();

Im Allgemeinen ist Downcasting keine gute Idee. Sie sollten es vermeiden. Wenn Sie es verwenden, fügen Sie besser einen Scheck hinzu:

if (animal instanceof Dog) {
    Dog dog = (Dog) animal;
}
Bozho
quelle
Der folgende Code generiert jedoch einen Kompilierungsfehler. Dog dog = new Animal (); (inkompatible Typen) .aber in dieser Situation identifiziert der Compiler, dass Animal eine Superklasse und Dog eine Unterklasse ist. Die Zuordnung ist also falsch. Aber wenn wir Dog dog = (Dog) animal werfen; es akzeptiert. Bitte erklären Sie mir dies
Saravanan
3
Ja, weil Animal eine Superklasse ist. Nicht jedes Tier ist ein Hund, oder? Sie können Klassen nur nach ihren Typen oder Supertypen referenzieren. Nicht ihre Untertypen.
Bozho
43

Um diese Art von ClassCastException zu vermeiden, haben Sie:

class A
class B extends A

Sie können in B einen Konstruktor definieren, der ein Objekt von A nimmt. Auf diese Weise können wir die "Umwandlung" durchführen, z.

public B(A a) {
    super(a.arg1, a.arg2); //arg1 and arg2 must be, at least, protected in class A
    // If B class has more attributes, then you would initilize them here
}
Caumons
quelle
24

Ausarbeitung der Antwort von Michael Berry.

Dog d = (Dog)Animal; //Compiles but fails at runtime

Hier sagen Sie dem Compiler "Vertrau mir. Ich weiß, dass er dsich wirklich auf ein DogObjekt bezieht ", obwohl dies nicht der Fall ist. Denken Sie daran, dass der Compiler gezwungen ist, uns zu vertrauen, wenn wir einen Downcast ausführen .

Der Compiler kennt nur den deklarierten Referenztyp. Die JVM zur Laufzeit weiß, was das Objekt wirklich ist.

Wenn die JVM zur Laufzeit herausfindet, dass sich die Dog dtatsächlich auf Animalein DogObjekt bezieht und nicht auf ein Objekt, das sie sagt. Hey ... du hast den Compiler angelogen und ein großes Fett geworfen ClassCastException.

Wenn Sie also niedergeschlagen sind, sollten Sie einen instanceofTest verwenden, um ein Versagen zu vermeiden.

if (animal instanceof Dog) { Dog dog = (Dog) animal; }

Jetzt kommt uns eine Frage in den Sinn. Warum erlaubt der Höllen-Compiler den Downcast, wenn er irgendwann einen werfen wird java.lang.ClassCastException?

Die Antwort lautet, dass der Compiler lediglich überprüfen kann, ob sich die beiden Typen im selben Vererbungsbaum befinden. Je nachdem, welcher Code vor dem Downcast gekommen sein könnte, ist es also möglich, dass dieser animalTyp vorliegt dog.

Der Compiler muss zulassen, dass Dinge zur Laufzeit funktionieren.

Betrachten Sie das folgende Code-Snipet:

public static void main(String[] args) 
{   
    Dog d = getMeAnAnimal();// ERROR: Type mismatch: cannot convert Animal to Dog
    Dog d = (Dog)getMeAnAnimal(); // Downcast works fine. No ClassCastException :)
    d.eat();

}

private static Animal getMeAnAnimal()
{
    Animal animal = new Dog();
    return animal;
}

Wenn der Compiler jedoch sicher ist, dass die Umwandlung nicht funktionieren würde, schlägt die Kompilierung fehl. IE Wenn Sie versuchen, Objekte in verschiedene Vererbungshierarchien umzuwandeln

String s = (String)d; // ERROR : cannot cast for Dog to String

Im Gegensatz zu Downcasting funktioniert Upcasting implizit, da Sie beim Upcasting implizit die Anzahl der Methoden einschränken, die Sie aufrufen können, im Gegensatz zum Downcasting, was impliziert, dass Sie später möglicherweise eine spezifischere Methode aufrufen möchten.

Dog d = new Dog(); Animal animal1 = d; // Works fine with no explicit cast Animal animal2 = (Animal) d; // Works fine with n explicit cast

Beide oben genannten Upcasts funktionieren ausnahmslos einwandfrei, da ein Hund IS-A Animal ist, was ein Tier kann, was ein Hund kann. Aber es ist nicht wahr umgekehrt.

Zeeshan
quelle
1

Der Code generiert einen Kompilierungsfehler, da Ihr Instanztyp ein Tier ist:

Animal animal=new Animal();

Downcasting ist in Java aus mehreren Gründen nicht zulässig. Siehe hier für Details.

Simeon
quelle
5
Es gibt keinen Kompilierungsfehler, das ist der Grund für seine Frage
Clarence Liu
1

Wie erklärt ist es nicht möglich. Wenn Sie eine Methode der Unterklasse verwenden möchten, prüfen Sie die Möglichkeit, die Methode zur Oberklasse hinzuzufügen (möglicherweise leer), und rufen Sie von den Unterklassen auf, um dank Polymorphismus das gewünschte Verhalten (Unterklasse) zu erhalten. Wenn Sie also d.method () aufrufen, ist der Aufruf ohne Casting erfolgreich. Falls das Objekt jedoch kein Hund ist, gibt es kein Problem

Fresko
quelle
0

So entwickeln Sie die Antwort von @Caumons:

Stellen Sie sich vor, eine Vaterklasse hat viele Kinder und es besteht die Notwendigkeit, dieser Klasse ein gemeinsames Feld hinzuzufügen. Wenn Sie den genannten Ansatz in Betracht ziehen, sollten Sie nacheinander zu jeder Kinderklasse gehen und ihre Konstruktoren für das neue Feld umgestalten. Daher ist diese Lösung in diesem Szenario keine vielversprechende Lösung

Schauen Sie sich nun diese Lösung an.

Ein Vater kann von jedem Kind ein Selbstobjekt erhalten. Hier ist eine Vaterklasse:

public class Father {

    protected String fatherField;

    public Father(Father a){
        fatherField = a.fatherField;
    }

    //Second constructor
    public Father(String fatherField){
        this.fatherField = fatherField;
    }

    //.... Other constructors + Getters and Setters for the Fields
}

Hier ist unsere untergeordnete Klasse, die einen ihrer Vaterkonstruktoren implementieren soll, in diesem Fall den oben genannten Konstruktor:

public class Child extends Father {

    protected String childField;

    public Child(Father father, String childField ) {
        super(father);
        this.childField = childField;
    }

    //.... Other constructors + Getters and Setters for the Fields

    @Override
    public String toString() {
        return String.format("Father Field is: %s\nChild Field is: %s", fatherField, childField);
    }
}

Jetzt testen wir die Anwendung:

public class Test {
    public static void main(String[] args) {
        Father fatherObj = new Father("Father String");
        Child child = new Child(fatherObj, "Child String");
        System.out.println(child);
    }
}

Und hier ist das Ergebnis:

Father Field ist: Father String

Untergeordnetes Feld ist: Untergeordnete Zeichenfolge

Jetzt können Sie der Vaterklasse ganz einfach neue Felder hinzufügen, ohne sich Sorgen machen zu müssen, dass die Codes Ihrer Kinder nicht mehr funktionieren.

Mehdi
quelle