conversión explícita de superclase a subclase

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

La asignación Dog dog = (Dog) animal;no genera un error de compilación, pero en tiempo de ejecución genera un ClassCastException. ¿Por qué el compilador no puede detectar este error?

saravanan
fuente
51
USTED le dice al compilador que NO detecte el error.
Mauricio

Respuestas:

326

Al usar un elenco básicamente le estás diciendo al compilador "confía en mí. Soy un profesional, sé lo que estoy haciendo y sé que aunque no puedes garantizarlo, te estoy diciendo que esta animalvariable es definitivamente va a ser un perro ".

Dado que el animal no es en realidad un perro (es un animal, podrías hacerlo Animal animal = new Dog();y sería un perro), el VM lanza una excepción en tiempo de ejecución porque has violado esa confianza (le dijiste al compilador que todo estaría bien y es ¡no!)

El compilador es un poco más inteligente que aceptar ciegamente todo, si intentas lanzar objetos en diferentes jerarquías de herencia (por ejemplo, lanzar un Perro a una Cadena), entonces el compilador te lo devolverá porque sabe que nunca podría funcionar.

Debido a que básicamente está evitando que el compilador se queje, cada vez que emite es importante verificar que no causará un ClassCastExceptional usar instanceofuna declaración if (o algo por el estilo).

Michael Berry
fuente
Gracias pero necesitas un perro extendido de Animal si no, no funciona :)
entrega el
66
Me encanta lo dramático que lo hiciste sonar
Hendra Anggrian
3
@delive Por supuesto que sí, pero según la pregunta, Dog se extiende desde Animal!
Michael Berry
52

Porque teóricamente Animal animal puede ser un perro:

Animal animal = new Dog();

En general, no es una buena idea abatir. Deberías evitarlo. Si lo usa, es mejor que incluya un cheque:

if (animal instanceof Dog) {
    Dog dog = (Dog) animal;
}
Bozho
fuente
pero el siguiente código genera un error de compilación Dog dog = new Animal (); (tipos incompatibles). pero en esta situación, el compilador identifica que Animal es una superclase y Dog es una subclase, por lo que la asignación es incorrecta. pero cuando lanzamos Dog dog = (Dog) animal; acepta. explícame sobre esto
saravanan
3
Sí, porque Animal es una superclase. No todos los animales son perros, ¿verdad? Puede referirse a las clases solo por sus tipos o sus supertipos. No son sus subtipos.
Bozho 01 de
43

Para evitar este tipo de ClassCastException, si tiene:

class A
class B extends A

Puede definir un constructor en B que tome un objeto de A. De esta manera podemos hacer el "lanzamiento", por ejemplo:

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

Elaborando la respuesta dada por Michael Berry.

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

Aquí le estás diciendo al compilador "Confía en mí. Sé dque realmente se refiere a un Dogobjeto", aunque no lo es. Recuerde que el compilador se ve obligado a confiar en nosotros cuando hacemos un downcast .

El compilador solo conoce el tipo de referencia declarado. La JVM en tiempo de ejecución sabe cuál es realmente el objeto.

Entonces, cuando la JVM en el tiempo de ejecución descubre que en Dog drealidad se refiere a un objeto Animaly no a un Dogobjeto, dice. Oye ... mentiste al compilador y arrojas una gran grasa ClassCastException.

Entonces, si está abatido, debe usar la instanceofprueba para evitar arruinarlo.

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

Ahora una pregunta viene a nuestra mente. ¿Por qué el compilador del infierno está permitiendo el downcast cuando eventualmente lanzará un java.lang.ClassCastException?

La respuesta es que todo lo que el compilador puede hacer es verificar que los dos tipos estén en el mismo árbol de herencia, por lo que, dependiendo del código que haya llegado antes del downcast, es posible que animalsea ​​de tipo dog.

El compilador debe permitir cosas que posiblemente funcionen en tiempo de ejecución.

Considere el siguiente fragmento 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;
}

Sin embargo, si el compilador está seguro de que el reparto no funcionaría, la compilación fallará. IE Si intenta convertir objetos en diferentes jerarquías de herencia

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

A diferencia del downcasting, el upcasting funciona implícitamente porque cuando realiza un upcast está restringiendo implícitamente el número de métodos que puede invocar, en contraposición al downcasting, lo que implica que más adelante, es posible que desee invocar un método más 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

Los dos cambios anteriores funcionarán bien sin ninguna excepción porque un perro es un animal IS-A, lo que un animal puede hacer, un perro puede hacer. Pero no es cierto, viceversa.

Zeeshan
fuente
1

El código genera un error de compilación porque su tipo de instancia es un Animal:

Animal animal=new Animal();

Downcasting no está permitido en Java por varias razones. Ver aquí para más detalles.

Simeon
fuente
55
No hay error de compilación, esa es la razón de su pregunta
Clarence Liu el
1

Como se explicó, no es posible. Si desea utilizar un método de la subclase, evalúe la posibilidad de agregar el método a la superclase (puede estar vacío) y llame desde las subclases obteniendo el comportamiento que desea (subclase) gracias al polimorfismo. Por lo tanto, cuando llame a d.method (), la llamada tendrá éxito sin realizar la conversión, pero en caso de que el objeto no sea un perro, no habrá ningún problema

Fresko
fuente
0

Para desarrollar la respuesta de @Caumons:

Imagine que una clase de padre tiene muchos hijos y es necesario agregar un campo común a esa clase. Si considera el enfoque mencionado, debe ir a cada clase de niños uno por uno y refactorizar sus constructores para el nuevo campo. por lo tanto, esa solución no es una solución prometedora en este escenario

Ahora eche un vistazo a esta solución.

Un padre puede recibir un objeto propio de cada hijo. Aquí hay una clase de padre:

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
}

Aquí está nuestra clase hija que debería implementar uno de sus constructores padre, en este caso el constructor mencionado anteriormente:

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

Ahora probamos la aplicación:

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

Y aqui esta el resultado :

Father Field es: Father String

El campo hijo es: cadena infantil

Ahora puede agregar fácilmente nuevos campos a la clase padre sin tener que preocuparse de que se rompan los códigos de sus hijos;

Mehdi
fuente