casting explicite de la super classe à la sous-classe

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;
    }
}

L'affectation Dog dog = (Dog) animal;ne génère pas d'erreur de compilation, mais au moment de l'exécution, elle génère un fichier ClassCastException. Pourquoi le compilateur ne peut-il pas détecter cette erreur?

Saravanan
la source
51
VOUS dites au compilateur de NE PAS détecter l'erreur.
Mauricio

Réponses:

326

En utilisant un casting, vous dites essentiellement au compilateur "faites-moi confiance. Je suis un professionnel, je sais ce que je fais et je sais que même si vous ne pouvez pas le garantir, je vous dis que cette animalvariable est définitivement va être un chien. "

Puisque l'animal n'est pas réellement un chien (c'est un animal, vous pourriez le faire Animal animal = new Dog();et ce serait un chien), la VM lève une exception à l'exécution parce que vous avez violé cette confiance (vous avez dit au compilateur que tout irait bien et c'est ne pas!)

Le compilateur est un peu plus intelligent que d'accepter tout aveuglément, si vous essayez de convertir des objets dans différentes hiérarchies d'héritage (transtyper un chien en chaîne par exemple), le compilateur vous le renverra car il sait que cela ne pourrait jamais fonctionner.

Étant donné que vous empêchez essentiellement le compilateur de se plaindre, chaque fois que vous lancez un cast, il est important de vérifier que vous ne provoquerez pas de ClassCastExceptionen utilisant instanceofune instruction if (ou quelque chose à cet effet.)

Michael Berry
la source
Merci mais vous avez besoin d'une extension de chien de Animal sinon, pas de travail :)
livraison
66
J'adore la façon dont vous l'avez fait sonner
Hendra Anggrian
3
@delive Bien sûr , vous le faites, mais par la question, Dog ne s'étendre à partir Animal!
Michael Berry
52

Parce que théoriquement Animal animal peut être un chien:

Animal animal = new Dog();

En règle générale, le downcasting n'est pas une bonne idée. Vous devriez l'éviter. Si vous l'utilisez, vous feriez mieux d'inclure un chèque:

if (animal instanceof Dog) {
    Dog dog = (Dog) animal;
}
Bozho
la source
mais le code suivant génère une erreur de compilation Dog dog = new Animal (); (types incompatibles) .Mais dans cette situation, le compilateur identifie Animal est une super-classe et Dog est une sous-classe.Ainsi, l'affectation est incorrecte.Mais lorsque nous lançons Dog dog = (Dog) animal; il accepte. veuillez m'expliquer à ce sujet
saravanan
3
oui, car Animal est une superclasse. Tous les animaux ne sont pas des chiens, non? Vous ne pouvez faire référence aux classes que par leurs types ou leurs supertypes. Pas leurs sous-types.
Bozho
43

Afin d'éviter ce type d'exception ClassCastException, si vous avez:

class A
class B extends A

Vous pouvez définir un constructeur en B qui prend un objet de A. De cette façon, nous pouvons faire le "cast" par exemple:

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
la source
24

Élaboration de la réponse donnée par Michael Berry.

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

Ici, vous dites au compilateur "Faites-moi confiance. Je sais que cela dfait vraiment référence à un Dogobjet" bien que ce ne soit pas le cas. N'oubliez pas que le compilateur est obligé de nous faire confiance lorsque nous faisons un downcast .

Le compilateur ne connaît que le type de référence déclaré. La JVM au moment de l'exécution sait ce qu'est réellement l'objet.

Ainsi, lorsque la JVM au moment de l'exécution comprend que le Dog dfait référence à Animalun Dogobjet et non à un objet, il le dit. Hé ... tu as menti au compilateur et jette un gros morceau ClassCastException.

Donc, si vous êtes abattu, vous devez utiliser le instanceoftest pour éviter de vous tromper.

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

Maintenant, une question nous vient à l'esprit. Pourquoi diable le compilateur autorise-t-il le downcast quand finalement il va lancer un java.lang.ClassCastException?

La réponse est que tout ce que le compilateur peut faire est de vérifier que les deux types sont dans la même arborescence d'héritage, donc en fonction du code qui aurait pu venir avant le downcast, il est possible qu'il animalsoit de type dog.

Le compilateur doit autoriser les choses qui pourraient fonctionner au moment de l'exécution.

Considérez l'extrait de code suivant:

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;
}

Cependant, si le compilateur est sûr que la distribution ne fonctionnerait pas, la compilation échouera. IE Si vous essayez de convertir des objets dans différentes hiérarchies d'héritage

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

Contrairement à la conversion descendante, la conversion ascendante fonctionne implicitement car lorsque vous effectuez une conversion ascendante, vous limitez implicitement le nombre de méthodes que vous pouvez invoquer, contrairement à la méthode descendante, ce qui implique que plus tard, vous souhaiterez peut-être invoquer une méthode plus spécifique.

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

Les deux upcast ci-dessus fonctionneront très bien sans aucune exception car un chien est un animal, tout comme un animal peut le faire, un chien peut le faire. Mais ce n'est pas vrai vica-versa.

Zeeshan
la source
1

Le code génère une erreur de compilation car votre type d'instance est un animal:

Animal animal=new Animal();

Le downcasting n'est pas autorisé en Java pour plusieurs raisons. Voir ici pour plus de détails.

Siméon
la source
5
Il n'y a pas d'erreur de compilation, c'est la raison de sa question
Clarence Liu
1

Comme expliqué, ce n'est pas possible. Si vous souhaitez utiliser une méthode de la sous-classe, évaluez la possibilité d'ajouter la méthode à la superclasse (peut être vide) et appelez depuis les sous-classes en obtenant le comportement souhaité (sous-classe) grâce au polymorphisme. Donc quand vous appelez d.method () l'appel réussira sans lancer, mais au cas où l'objet ne serait pas un chien, il n'y aura pas de problème

fresko
la source
0

Pour développer la réponse de @Caumons:

Imaginez qu'une classe père ait plusieurs enfants et qu'il soit nécessaire d'ajouter un champ commun à cette classe. Si vous considérez l'approche mentionnée, vous devriez aller à chaque classe d'enfants un par un et refactoriser leurs constructeurs pour le nouveau champ. donc cette solution n'est pas une solution prometteuse dans ce scénario

Jetez maintenant un œil à cette solution.

Un père peut recevoir un objet personnel de chaque enfant. Voici une classe père:

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
}

Voici notre classe enfant qui devrait implémenter l'un de son constructeur père, dans ce cas le constructeur susmentionné:

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);
    }
}

Maintenant, nous testons l'application:

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);
    }
}

Et voici le résultat :

Le champ Father est: Father String

Le champ enfant est: Chaîne enfant

Maintenant, vous pouvez facilement ajouter de nouveaux champs à la classe père sans vous soucier de vos codes enfants à casser;

Mehdi
la source