conversão explícita da super classe à subclasse

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

A atribuição Dog dog = (Dog) animal;não gera um erro de compilação, mas em tempo de execução, gera a ClassCastException. Por que o compilador não pode detectar esse erro?

saravanan
fonte
50
VOCÊ está dizendo ao compilador para NÃO detectar o erro.
Mauricio

Respostas:

326

Ao usar um elenco, você está basicamente dizendo ao compilador "confie em mim. Sou profissional, sei o que estou fazendo e sei que, embora você não possa garantir isso, estou lhe dizendo que isso animal variável é definitivamente vai ser um cachorro. "

Como o animal não é realmente um cachorro (é um animal, você poderia fazer Animal animal = new Dog();e seria um cachorro), a VM lança uma exceção em tempo de execução porque você violou essa confiança (você disse ao compilador que tudo ficaria bem e é não!)

O compilador é um pouco mais inteligente do que aceitar cegamente tudo, se você tentar converter objetos em hierarquias de herança diferentes (converter um Dog em uma String, por exemplo), o compilador o jogará de volta para você, porque sabe que isso nunca poderia funcionar.

Como você está basicamente impedindo que o compilador se queixe, toda vez que você lança, é importante verificar se você não causará um ClassCastExceptionusando instanceofuma instrução if (ou algo nesse sentido).

Michael Berry
fonte
Obrigado, mas você precisa dog estendem de animal, se não, não trabalho :)
entreg
66
Eu amo como dramático que você fez soar
Hendra Anggrian
3
@delive Claro que sim, mas de acordo com a pergunta, Dog se estende de Animal!
Michael Berry
52

Porque teoricamente Animal animal pode ser um cachorro:

Animal animal = new Dog();

Geralmente, fazer downcasting não é uma boa ideia. Você deveria evitá-lo. Se você usá-lo, é melhor incluir uma verificação:

if (animal instanceof Dog) {
    Dog dog = (Dog) animal;
}
Bozho
fonte
mas o código a seguir gera erro de compilação Dog dog = new Animal (); (tipos incompatíveis) .mas, nesta situação, o compilador identifica Animal como uma super classe e Dog como uma subclasse. Portanto, a atribuição está errada. ele aceita .please me explicar sobre isso
saravanan
3
sim, porque Animal é superclasse. Nem todo animal é um cachorro, certo? Você pode se referir às classes apenas por seus tipos ou supertipos. Não são seus subtipos.
Bozho
43

Para evitar esse tipo de ClassCastException, se você tiver:

class A
class B extends A

Você pode definir um construtor em B que aceite um objeto A. Assim, podemos fazer o "cast", por exemplo:

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
fonte
24

Elaborando a resposta dada por Michael Berry.

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

Aqui você está dizendo ao compilador "Confie em mim. Eu sei que drealmente está se referindo a um Dogobjeto", embora não seja. Lembre-se de que o compilador é forçado a confiar em nós quando fazemos um downcast .

O compilador conhece apenas o tipo de referência declarado. A JVM em tempo de execução sabe qual é realmente o objeto.

Portanto, quando a JVM no tempo de execução descobre que na Dog dverdade se refere a Animalum Dogobjeto e não a um objeto, diz. Ei ... você mentiu para o compilador e jogou muita gordura ClassCastException.

Portanto, se você está instanceoffazendo downcast, deve usar o teste para evitar estragar.

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

Agora uma pergunta vem à nossa mente. Por que o compilador infernal está permitindo o downcast quando, eventualmente, ele vai lançar umjava.lang.ClassCastException ?

A resposta é que tudo o que o compilador pode fazer é verificar se os dois tipos estão na mesma árvore de herança, portanto, dependendo do código que possa ter vindo antes do downcast, é possível que animalseja do tipo dog.

O compilador deve permitir coisas que possam funcionar em tempo de execução.

Considere o seguinte trecho de código:

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

No entanto, se o compilador tiver certeza de que o elenco não funcionaria, a compilação falhará. IE Se você tentar converter objetos em diferentes hierarquias de herança

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

Diferentemente da downcasting, a upcasting funciona implicitamente porque, quando você upcast, está implicitamente restringindo o número de métodos que você pode invocar, ao contrário do downcasting, o que implica que, posteriormente, você poderá invocar um método mais específico.

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

Ambos os upcast acima funcionarão bem, sem exceção, porque um cão é um animal, anithing um animal pode fazer, um cão pode fazer. Mas não é verdade vica-versa.

Zeeshan
fonte
1

O código gera um erro de compilação porque seu tipo de instância é um Animal:

Animal animal=new Animal();

O downcasting não é permitido em Java por vários motivos. Veja aqui para detalhes.

Simeon
fonte
5
Não há erro de compilação, que é a razão para a sua pergunta
Clarence Liu
1

Como explicado, não é possível. Se você deseja usar um método da subclasse, avalie a possibilidade de adicionar o método à superclasse (pode estar vazia) e chame as subclasses obtendo o comportamento desejado (subclasse) graças ao polimorfismo. Portanto, quando você chama d.method (), a chamada será bem-sucedida sem a transmissão, mas, caso o objeto não seja um cachorro, não haverá problema.

fresko
fonte
0

Para desenvolver a resposta de @Caumons:

Imagine que uma classe paterna tenha muitos filhos e é necessário adicionar um campo comum a essa classe. Se você considerar a abordagem mencionada, deve ir para cada classe de crianças uma por uma e refatorar seus construtores para o novo campo. portanto, essa solução não é uma solução promissora neste cenário

Agora, dê uma olhada nesta solução.

Um pai pode receber um auto-objeto de cada filho. Aqui está uma classe pai:

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
}

Aqui está nossa classe filho que deve implementar um de seus construtores pai, neste caso o construtor mencionado acima:

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

Agora testamos a aplicação:

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 aqui está o resultado :

Father Field é: Father String

O campo filho é: String filho

Agora você pode facilmente adicionar novos campos à classe pai sem se preocupar com a quebra dos códigos de seus filhos;

Mehdi
fonte