Downcasting en Java


179

La conversión ascendente está permitida en Java, sin embargo, la reducción genera un error de compilación.

El error de compilación se puede eliminar agregando un reparto, pero de todos modos se rompería en el tiempo de ejecución.

En este caso, ¿por qué Java permite el downcasting si no se puede ejecutar en tiempo de ejecución?
¿Hay algún uso práctico para este concepto?

public class demo {
  public static void main(String a[]) {
      B b = (B) new A(); // compiles with the cast, 
                         // but runtime exception - java.lang.ClassCastException
  }
}

class A {
  public void draw() {
    System.out.println("1");
  }

  public void draw1() {
    System.out.println("2");
  }
}

class B extends A {
  public void draw() {
    System.out.println("3");
  }
  public void draw2() {
    System.out.println("4");
  }
}

9
Un fragmento de código de ejemplo más el error haría que esta sea una mejor pregunta para las personas que están tratando de aprender los conceptos.
Bob Cross

3
+1 para el comentario de Bob. La pregunta no está clara en absoluto.
Jon Skeet

Veo que el ejemplo anterior está tomado de velocityreviews.com/forums/t151266-downcasting-problem.html que ya tiene algunas buenas respuestas.
PhiLho

2
@PhiLho: la intención principal de Joel era obtener todas las excelentes preguntas y respuestas bajo un paraguas común. No importa si la pregunta / código / respuestas ya están publicados en otros sitios. Espero que entiendas el tema, de lo contrario escucha los podcasts de Joel.
Omnipotente

Edite esto para que los fragmentos de código estén sangrados por cuatro espacios. Eso arreglará el formato.
delgado

Respuestas:


298

Se permite el downcasting cuando existe la posibilidad de que tenga éxito en tiempo de ejecución:

Object o = getSomeObject(),
String s = (String) o; // this is allowed because o could reference a String

En algunos casos esto no tendrá éxito:

Object o = new Object();
String s = (String) o; // this will fail at runtime, because o doesn't reference a String

Cuando un lanzamiento (como este último) falla en tiempo de ejecución ClassCastException, se lanzará un .

En otros casos funcionará:

Object o = "a String";
String s = (String) o; // this will work, since o references a String

Tenga en cuenta que algunos lanzamientos se rechazarán en el momento de la compilación, porque nunca tendrán éxito en absoluto:

Integer i = getSomeInteger();
String s = (String) i; // the compiler will not allow this, since i can never reference a String.

Object o = new Object(); String s = (String) o;Me está funcionando bien ...: O ¿Cómo?
Asif Mushtaq

@ Desconocido: no debería. Vuelva a verificar que realmente compiló y ejecutó esa versión y si aún puede reproducirla, publique una pregunta por separado (con un SSCCE ).
Joachim Sauer

@JoachimSauer, ¿qué quieres decir con esa versión? Estoy usando Java 8.
Asif Mushtaq

1
@UnKnown: quiero decir que el código que publicaste no debería ejecutarse (se compilará, pero arrojará una excepción en tiempo de ejecución). Estos comentarios no son el espacio para depurar eso. Por favor, publique una pregunta por separado.
Joachim Sauer

¿Cómo falla el casting en tiempo de ejecución? Establece la referencia del objeto de destino en nulo? ¿Lanza una excepción?
CygnusX1

17

Usando su ejemplo, podría hacer:

public void doit(A a) {
    if(a instanceof B) {
        // needs to cast to B to access draw2 which isn't present in A
        // note that this is probably not a good OO-design, but that would
        // be out-of-scope for this discussion :)
        ((B)a).draw2();
    }
    a.draw();
}

Acabo de aprender la importancia de la instancia de cuando mi clase abstracta estaba siendo extendida por múltiples clases y quería usar métodos exclusivos de esas clases, al tiempo que me refería al tipo de clase abstracta. No utilizo instanceof tuve una excepción de lanzamiento de clase
Tarun

16

Creo que esto se aplica a todos los idiomas estáticamente escritos:

String s = "some string";
Object o = s; // ok
String x = o; // gives compile-time error, o is not neccessarily a string
String x = (String)o; // ok compile-time, but might give a runtime exception if o is not infact a String

La tipología dice efectivamente: suponga que esta es una referencia a la clase de conversión y úsela como tal. Ahora, digamos que o es realmente un número entero, suponiendo que se trata de una cadena no tiene sentido y dará resultados inesperados, por lo tanto, debe haber una verificación de tiempo de ejecución y una excepción para notificar al entorno de tiempo de ejecución que algo está mal.

En el uso práctico, puede escribir código trabajando en una clase más general, pero convertirlo en una subclase si sabe qué subclase es y necesita tratarlo como tal. Un ejemplo típico es anular Object.equals (). Supongamos que tenemos una clase para automóvil:

@Override
boolean equals(Object o) {
    if(!(o instanceof Car)) return false;
    Car other = (Car)o;
    // compare this to other and return
}

Me gusta la palabra Realmente y editaré tu publicación para hacerla más
parecida

5

Todos podemos ver que el código que proporcionó no funcionará en tiempo de ejecución. Eso es porque sabemos que la expresión nuncanew A() puede ser un objeto de tipo .B

Pero no es así como lo ve el compilador. Para cuando el compilador verifica si el reparto está permitido, solo ve esto:

variable_of_type_B = (B)expression_of_type_A;

Y como otros han demostrado, ese tipo de elenco es perfectamente legal. La expresión de la derecha podría muy bien evaluar a un objeto de tipo B. El compilador ve eso Ay Btiene una relación de subtipo, por lo que con la vista de "expresión" del código, el reparto podría funcionar.

El compilador no considera el caso especial cuando sabe exactamente qué tipo de objeto expression_of_type_Arealmente tendrá. Simplemente ve el tipo estático como Ay considera que el tipo dinámico podría ser Ao cualquier descendiente A, incluido B.


3

En este caso, ¿por qué Java permite el downcasting si no se puede ejecutar en tiempo de ejecución?

Creo que esto se debe a que no hay forma de que el compilador sepa en tiempo de compilación si el elenco tendrá éxito o no. Para su ejemplo, es simple ver que el elenco fallará, pero hay otros momentos en que no está tan claro.

Por ejemplo, imagine que todos los tipos B, C y D extienden el tipo A, y luego un método public A getSomeA()devuelve una instancia de B, C o D dependiendo de un número generado aleatoriamente. El compilador no puede saber qué tipo de tiempo de ejecución exacto devolverá este método, por lo que si luego envía los resultados B, no hay forma de saber si el lanzamiento tendrá éxito (o fallará). Por lo tanto, el compilador debe asumir que los lanzamientos tendrán éxito.


2

@ Cartel original: vea los comentarios en línea.

public class demo 
{
    public static void main(String a[]) 
    {
        B b = (B) new A(); // compiles with the cast, but runtime exception - java.lang.ClassCastException 
        //- A subclass variable cannot hold a reference to a superclass  variable. so, the above statement will not work.

        //For downcast, what you need is a superclass ref containing a subclass object.
        A superClassRef = new B();//just for the sake of illustration
        B subClassRef = (B)superClassRef; // Valid downcast. 
    }
}

class A 
{
    public void draw() 
    {
        System.out.println("1");
    }

    public void draw1() 
    {
        System.out.println("2");
    }
}

class B extends A 
{
    public void draw() 
    {
        System.out.println("3");
    }

    public void draw2() 
    {
        System.out.println("4");
    }
}

2

Downcast funciona en el caso cuando se trata de un objeto upcasted. Upcasting:

int intValue = 10;
Object objValue = (Object) intvalue;

Así que ahora esta objValuevariable siempre se puede descartar intporque el objeto que se echó es un Integer,

int oldIntValue = (Integer) objValue;
// can be done 

pero debido a que objValuees un Objeto no se puede lanzar Stringporque intno se puede lanzar String.


0

Downcasting es muy útil en el siguiente fragmento de código. Lo uso todo el tiempo. Por lo tanto, probar que el downcasting es útil.

private static String printAll(LinkedList c)
{
    Object arr[]=c.toArray();
    String list_string="";
    for(int i=0;i<c.size();i++)
    {
        String mn=(String)arr[i];
        list_string+=(mn);
    }
    return list_string;
}

Almaceno String en la lista vinculada. Cuando recupero los elementos de la Lista vinculada, se devuelven los Objetos. Para acceder a los elementos como cadenas (o cualquier otro objeto de clase), el downcasting me ayuda.

Java nos permite compilar código abatido confiando en que estamos haciendo lo incorrecto. Aún así, si los humanos cometen un error, se detecta en tiempo de ejecución.


El uso de colecciones no genéricas en Java es el equivalente de void*punteros en C ++. No me parece una buena idea en absoluto.
Jezor

0

Considere el siguiente ejemplo

public class ClastingDemo {

/**
 * @param args
 */
public static void main(String[] args) {
    AOne obj = new Bone();
    ((Bone) obj).method2();
}
}

class AOne {
public void method1() {
    System.out.println("this is superclass");
}
}


 class Bone extends AOne {

public void method2() {
    System.out.println("this is subclass");
}
}

aquí creamos el objeto de la subclase Bone y lo asignamos a la referencia de superclase AOne y ahora la referencia de superclase no conoce el método método2 en la subclase, es decir, Bone durante el tiempo de compilación. Por lo tanto, debemos reducir esta referencia de superclase a la referencia de subclase la referencia resultante puede conocer la presencia de métodos en la subclase, es decir, Bone


AOne parece algo confuso. Por favor considere cambiar los nombres de sus clases a Perro y Animal o algo así
Kartik Chugh

0

Para hacer downcasting en Java y evitar excepciones en tiempo de ejecución, tome una referencia del siguiente código:

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

Aquí, Animal es la clase principal y Perro es la clase secundaria.
instanceof es una palabra clave que se usa para verificar si una variable de referencia contiene un tipo dado de referencia de objeto o no.


0

La transformación de conversión de objetos no es posible. Solamente

DownCasting1 _downCasting1 = (DownCasting1)((DownCasting2)downCasting1);

es posible

class DownCasting0 {
    public int qwe() {
        System.out.println("DownCasting0");
        return -0;
    }
}

class DownCasting1 extends DownCasting0 {
    public int qwe1() {
        System.out.println("DownCasting1");
        return -1;
    }
}

class DownCasting2 extends DownCasting1 {
    public int qwe2() {
        System.out.println("DownCasting2");
        return -2;
    }
}

public class DownCasting {

    public static void main(String[] args) {

        try {
            DownCasting0 downCasting0 = new DownCasting0();
            DownCasting1 downCasting1 = new DownCasting1();
            DownCasting2 downCasting2 = new DownCasting2();

            DownCasting0 a1 = (DownCasting0) downCasting2;
            a1.qwe(); //good

            System.out.println(downCasting0 instanceof  DownCasting2);  //false
            System.out.println(downCasting1 instanceof  DownCasting2);  //false
            System.out.println(downCasting0 instanceof  DownCasting1);  //false

            DownCasting2 _downCasting1= (DownCasting2)downCasting1;     //good
            DownCasting1 __downCasting1 = (DownCasting1)_downCasting1;  //good
            DownCasting2 a3 = (DownCasting2) downCasting0; // java.lang.ClassCastException

            if(downCasting0 instanceof  DownCasting2){ //false
                DownCasting2 a2 = (DownCasting2) downCasting0;
                a2.qwe(); //error
            }

            byte b1 = 127;
            short b2 =32_767;
            int b3 = 2_147_483_647;
//          long _b4 = 9_223_372_036_854_775_807; //int large number max 2_147_483_647
            long b4 = 9_223_372_036_854_775_807L;
//          float _b5 = 3.4e+038; //double default
            float b5 = 3.4e+038F; //Sufficient for storing 6 to 7 decimal digits
            double b6 = 1.7e+038;
            double b7 = 1.7e+038D; //Sufficient for storing 15 decimal digits

            long c1 = b3;
            int c2 = (int)b4;

            //int       4 bytes     Stores whole numbers from -2_147_483_648 to 2_147_483_647
            //float     4 bytes     Stores fractional numbers from 3.4e−038 to 3.4e+038. Sufficient for storing 6 to 7 decimal digits
            float c3 = b3; //logic error
            double c4 = b4; //logic error


        } catch (Throwable e) {
            e.printStackTrace();
        }
    }

}
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.