casting esplicito dalla superclasse alla sottoclasse


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'assegnazione Dog dog = (Dog) animal;non genera un errore di compilazione, ma in fase di esecuzione genera un ClassCastException. Perché il compilatore non è in grado di rilevare questo errore?


51
Stai dicendo al compilatore di NON rilevare l'errore.
Mauricio,

Risposte:


326

Usando un cast stai essenzialmente dicendo al compilatore "fidati di me. Sono un professionista, so cosa sto facendo e so che, anche se non puoi garantirlo, ti sto dicendo che questa animalvariabile è sicuramente sarà un cane ".

Dal momento che l'animale non è in realtà un cane (è un animale, potresti farlo Animal animal = new Dog();e sarebbe un cane) la VM lancia un'eccezione in fase di esecuzione perché hai violato quella fiducia (hai detto al compilatore che tutto sarebbe ok ed è non!)

Il compilatore è un po 'più intelligente dell'accettazione cieca di tutto, se provi a lanciare oggetti in diverse gerarchie di ereditarietà (ad esempio, lanciare un cane su una stringa), il compilatore te lo restituirà perché sa che non potrebbe mai funzionare.

Poiché essenzialmente stai semplicemente impedendo al compilatore di lamentarsi, ogni volta che esegui il cast è importante verificare che non causerai a ClassCastException usando instanceofun'istruzione if (o qualcosa in tal senso).


Grazie ma hai bisogno che il cane si estenda da Animal se non, non funziona :)
consegna il

66
Adoro quanto sia stato drammatico il tuo suono
Hendra Anggrian

3
@delive Certo che lo fai, ma come per la domanda, Dog si estende da Animal!
Michael Berry,

52

Perché teoricamente Animal animal può essere un cane:

Animal animal = new Dog();

In generale, il downcasting non è una buona idea. Dovresti evitarlo. Se lo usi, è meglio includere un segno di spunta:

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

ma il codice seguente genera un errore di compilazione Dog dog = new Animal (); (tipi incompatibili). ma in questa situazione il compilatore identifica Animal è una superclasse e Dog è una sottoclasse. Quindi l'assegnazione è sbagliata. Ma quando lanciamo Dog dog = (Dog) animal; accetta. Per
favore

3
si, perché Animal è superclasse. Non tutti gli animali sono cani, giusto? Puoi fare riferimento alle classi solo per tipo o supertipo. Non i loro sottotipi.
Bozho,

43

Per evitare questo tipo di ClassCastException, se si dispone di:

class A
class B extends A

Puoi definire un costruttore in B che prende un oggetto di A. In questo modo possiamo fare il "cast" es:

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
}

24

Elaborazione della risposta data da Michael Berry.

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

Qui stai dicendo al compilatore "Fidati di me. So che dsi riferisce davvero a un Dogoggetto" anche se non lo è. Ricorda che il compilatore è costretto a fidarsi di noi quando facciamo un downcast .

Il compilatore conosce solo il tipo di riferimento dichiarato. La JVM in fase di esecuzione sa cosa sia realmente l'oggetto.

Quindi quando la JVM in fase di esecuzione capisce che si Dog dsta effettivamente riferendo a un oggetto Animale non a un Dogoggetto, lo dice. Ehi ... hai mentito al compilatore e hai lanciato un grosso grassoClassCastException .

Quindi, se stai effettuando il downcasting, dovresti usare instanceoftest per evitare di sbagliare.

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

Ora ci viene in mente una domanda. Perché il compilatore infernale sta permettendo il downcast quando alla fine lancerà ajava.lang.ClassCastException ?

La risposta è che tutto ciò che il compilatore può fare è verificare che i due tipi si trovino nello stesso albero di ereditarietà, quindi a seconda di qualunque codice sia arrivato prima del downcast, è possibile che animalsia di tipo dog.

Il compilatore deve consentire cose che potrebbero funzionare in fase di esecuzione.

Considera il seguente snipet di codice:

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

Tuttavia, se il compilatore è sicuro che il cast non funzionerà, la compilazione fallirà. IE Se si tenta di eseguire il cast di oggetti in gerarchie ereditarie diverse

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

A differenza del downcasting, l'upcasting funziona implicitamente perché quando esegui l'upgrade stai implicitamente limitando il numero di metodi che puoi invocare, al contrario del downcasting, il che implica che in seguito potresti voler invocare un metodo più specifico.

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

Entrambi gli upcast di cui sopra funzioneranno bene senza alcuna eccezione perché un cane IS-A Animal, anithing an Animal sa fare, un cane può fare. Ma non è vero viceversa.


1

Il codice genera un errore di compilazione perché il tipo di istanza è un animale:

Animal animal=new Animal();

Il downcasting non è consentito in Java per diversi motivi. Vedi qui per i dettagli.


5
Non ci sono errori di compilazione, questo è il motivo della sua domanda
Clarence Liu,

1

Come spiegato, non è possibile. Se si desidera utilizzare un metodo della sottoclasse, valutare la possibilità di aggiungere il metodo alla superclasse (potrebbe essere vuoto) e chiamare dalle sottoclassi ottenendo il comportamento desiderato (sottoclasse) grazie al polimorfismo. Quindi quando chiami d.method () la chiamata avrà successo con il casting, ma nel caso in cui l'oggetto non sia un cane, non ci sarà un problema


0

Per sviluppare la risposta di @Caumons:

Immagina che una classe paterna abbia molti figli e che sia necessario aggiungere un campo comune a quella classe. Se consideri l'approccio menzionato, dovresti andare a ogni classe di bambini uno per uno e riformattare i loro costruttori per il nuovo campo. pertanto quella soluzione non è una soluzione promettente in questo scenario

Ora dai un'occhiata a questa soluzione.

Un padre può ricevere un oggetto personale da ogni bambino. Ecco una classe paterna:

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
}

Ecco la nostra classe figlio che dovrebbe implementare uno dei suoi costruttori padre, in questo caso il suddetto costruttore:

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

Ora testiamo l'applicazione:

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

E questo è il risultato :

Father Field è: Father String

Il campo figlio è: stringa figlio

Ora puoi facilmente aggiungere nuovi campi alla classe padre senza preoccuparti che i codici dei tuoi figli vengano infranti;

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.