web-dev-qa-db-ger.com

explizites Casting von der Superklasse in die Unterklasse

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; erzeugt keinen Kompilierungsfehler, generiert jedoch zur Laufzeit eine ClassCastException. Warum kann der Compiler diesen Fehler nicht erkennen?

123
saravanan

Durch die Verwendung eines Casts sagen Sie dem Compiler im Wesentlichen: "Vertrauen Sie mir. Ich bin ein Profi, ich weiß, was ich mache und ich weiß, obwohl Sie es nicht garantieren können, sage ich Ihnen, dass diese Variable animal ist definitiv ein Hund sein. "

Da das Tier nicht wirklich ein Hund ist (es ist ein Tier, könnten Sie Animal animal = new Dog(); und es wäre ein Hund), wirft die VM zur Laufzeit eine Ausnahme aus, weil Sie dieses Vertrauen verletzt haben (Sie haben der Compiler alles wäre ok und das ist es nicht!)

Der Compiler ist etwas intelligenter, als lediglich alles blind zu akzeptieren. Wenn Sie versuchen, Objekte in verschiedenen Erbschaftshierarchien umzuwandeln (z. B. einen Hund in einen String umzuwandeln), wird der Compiler Sie zurückwerfen, da er weiß, dass er niemals funktionieren könnte.

Da Sie den Compiler im Grunde nur davon abhalten, sich zu beschweren, ist es wichtig, bei jeder Umwandlung zu prüfen, dass Sie keinen ClassCastException durch die Verwendung von instanceof in einer if-Anweisung (oder einem entsprechenden Effekt) verursachen.

270
Michael Berry

Weil theoretisch Animal animalcan ein Hund sein kann:

Animal animal = new Dog();

Generell ist Downcasting keine gute Idee. Du solltest es vermeiden. Wenn Sie es verwenden, sollten Sie eine Überprüfung einschließen: 

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

Um diese Art von ClassCastException zu vermeiden, haben Sie Folgendes:

class A
class B extends A

Sie können einen Konstruktor in B definieren, der ein Objekt von A annimmt.

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
}
37
Caumons

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 d wirklich auf ein Dog Objekt verweist", obwohl dies nicht der Fall ist. Denken Sie daran, dass der Compiler gezwungen ist, uns zu vertrauen, wenn wir einen Downcast durchführen.

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

Wenn die JVM zur Laufzeit feststellt, dass sich Dog d Tatsächlich auf ein Animal und nicht auf ein Dog Objekt bezieht, heißt es. Hey ... du hast den Compiler angelogen und einen großen fetten ClassCastException geworfen.

Wenn du also einen Downcast machst, solltest du den Test instanceof verwenden, um ein Versagen zu vermeiden.

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

Nun kommt uns eine Frage in den Sinn. Warum erlaubt der Höllencompiler den Downcast, wenn er irgendwann einen Java.lang.ClassCastException Auslöst?

Die Antwort ist, dass der Compiler nur überprüfen kann, ob sich die beiden Typen in demselben Vererbungsbaum befinden. Je nachdem, welcher Code vor dem Downcast möglicherweise eingegangen ist, ist animal möglicherweise vom Typ dog.

Der Compiler muss Dinge zulassen, die möglicherweise 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 möglich ist, schlägt die Kompilierung fehl. I.E. Wenn Sie versuchen, Objekte in verschiedene Vererbungshierarchien umzuwandeln

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

Im Gegensatz zum Downcasting funktioniert das Upcasting implizit, da beim Upcasting die Anzahl der Methoden, die Sie aufrufen können, implizit eingeschränkt wird, während beim Downcasting später möglicherweise eine spezifischere Methode aufgerufen werden soll.

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 Effekte funktionieren ausnahmslos einwandfrei, da ein Hund ein Tier ist, was ein Tier kann, was ein Hund kann. Aber es ist nicht umgekehrt.

20
Zeeshan

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

Animal animal=new Animal();

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

2
Simeon

Wie erläutert, ist dies nicht möglich . Wenn Sie eine Methode der Unterklasse verwenden möchten, prüfen Sie die Möglichkeit, die Methode der Superklasse hinzuzufügen (möglicherweise leer), und rufen Sie die Unterklassen auf, um das gewünschte Verhalten zu erhalten (Unterklasse). dank polymorphism . Wenn Sie also d.method () aufrufen, wird der Aufruf ohne Casting gelingen, aber für den Fall, dass das Objekt kein Hund ist, gibt es kein Problem

1
fresko

Um die Antwort von @Caumons zu entwickeln:

Stellen Sie sich vor, eine Vaterklasse hat viele Kinder und es ist notwendig, ein gemeinsames .__ hinzuzufügen. Feld in diese Klasse. Wenn Sie den erwähnten Ansatz in Betracht ziehen, sollten Sie Gehen Sie nacheinander zu den einzelnen Kinderklassen und ändern Sie ihre Konstrukteure für das neue Feld. Daher ist diese Lösung in diesem Szenario keine vielversprechende Lösung

Nun werfen Sie einen Blick auf diese Lösung.

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

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 Kinderklasse, die einen von ihrem Vater implementieren sollte Konstruktor, in diesem Fall der zuvor genannte 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: 

Vater Feld ist: Vater String 

Untergeordnetes Feld ist: Untergeordnete Zeichenfolge

Jetzt können Sie problemlos neue Felder zur Vaterklasse hinzufügen, ohne sich Sorgen zu machen, dass die Codes Ihrer Kinder verletzt werden.

0
Mehdi