ejemplo de fundición de tipo explícito en java

He encontrado este ejemplo en http://www.javabeginner.com/learn-java/java-object-typecasting y en la parte donde se habla de fundición de tipos explícitos hay un ejemplo que me confunde.

El ejemplo:

class Vehicle { String name; Vehicle() { name = "Vehicle"; } } class HeavyVehicle extends Vehicle { HeavyVehicle() { name = "HeavyVehicle"; } } class Truck extends HeavyVehicle { Truck() { name = "Truck"; } } class LightVehicle extends Vehicle { LightVehicle() { name = "LightVehicle"; } } public class InstanceOfExample { static boolean result; static HeavyVehicle hV = new HeavyVehicle(); static Truck T = new Truck(); static HeavyVehicle hv2 = null; public static void main(String[] args) { result = hV instanceof HeavyVehicle; System.out.print("hV is an HeavyVehicle: " + result + "\n"); result = T instanceof HeavyVehicle; System.out.print("T is an HeavyVehicle: " + result + "\n"); result = hV instanceof Truck; System.out.print("hV is a Truck: " + result + "\n"); result = hv2 instanceof HeavyVehicle; System.out.print("hv2 is an HeavyVehicle: " + result + "\n"); hV = T; //Sucessful Cast form child to parent T = (Truck) hV; //Sucessful Explicit Cast form parent to child } } 

En la última línea, donde a T se le asigna la referencia hV y el tipo de transmisión como (Camión), ¿por qué dice en el comentario que se trata de un lanzamiento explícito con éxito de padres a hijos? Como entiendo, el casting (implícito o explícito) solo cambiará el tipo de objeto declarado, no el tipo real (que nunca debería cambiar, a menos que realmente asigne una nueva instancia de clase a la referencia de campo de ese objeto). Si hv ya tiene asignada una instancia de una clase HeavyVehicle, que es una súper clase de la clase Truck, ¿cómo puede este campo ser lanzado en una subclase más específica llamada Truck que se extiende desde la clase HeavyVehicle?

La forma en que lo entiendo es que el casting tiene el propósito de limitar el acceso a ciertos métodos de un objeto (instancia de clase). Por lo tanto, no puede convertir un objeto como una clase más específica que tiene más métodos que la clase asignada real del objeto. Eso significa que el objeto solo se puede convertir como una superclase o la misma clase que la clase desde la que se creó la instancia. ¿Es correcto o estoy equivocado aquí? Todavía estoy aprendiendo, así que no estoy seguro de si esta es la forma correcta de ver las cosas.

También entiendo que esto debería ser un ejemplo de downcasting, pero no estoy seguro de cómo funciona realmente si el tipo real no tiene los métodos de la clase a la que se está descifrando este objeto. ¿La conversión explícita de alguna manera cambia el tipo de objeto real (no solo el tipo declarado), por lo que este objeto ya no es una instancia de la clase HeavyVehicle pero ahora se convierte en una instancia de la clase Truck?

Referencia vs Objeto vs Tipos

La clave, para mí, es comprender la diferencia entre un objeto y sus referencias, o poner en otras palabras la diferencia entre un objeto y sus tipos.

Cuando creamos un objeto en Java, declaramos su verdadera naturaleza, que nunca cambiará. Pero cualquier objeto dado en Java es probable que tenga varios tipos. Algunos de estos tipos se dan obviamente gracias a la jerarquía de clases, otros no son tan obvios (es decir, generics, matrices).

Específicamente para los tipos de referencia, la jerarquía de clases dicta las reglas de subtipado. Por ejemplo, en su ejemplo, todos los camiones son vehículos pesados y todos los vehículos pesados ​​son vehículos . Por lo tanto, esta jerarquía de las relaciones is-a dicta que un camión tiene múltiples tipos compatibles .

Cuando creamos un Truck , definimos una “referencia” para tener acceso a él. Esta referencia debe tener uno de esos tipos compatibles.

 Truck t = new Truck(); //or HeavyVehicle hv = new Truck(); //or Vehicle h = new Truck() //or Object o = new Truck(); 

Entonces, el punto clave aquí es la comprensión de que la referencia al objeto no es el objeto mismo . La naturaleza del objeto que se está creando nunca va a cambiar. Pero podemos usar diferentes tipos de referencias compatibles para obtener acceso al objeto. Esta es una de las características del polymorphism aquí. Se puede acceder al mismo objeto a través de referencias de diferentes tipos “compatibles”.

Cuando hacemos cualquier tipo de casting, simplemente asumimos la naturaleza de esta compatibilidad entre diferentes tipos de referencias.

Upcasting o ampliación de la conversión de referencia

Ahora, teniendo una referencia de tipo Truck , podemos concluir fácilmente que siempre es compatible con una referencia de tipo Vehicle , porque todos los Camiones son Vehículos . Por lo tanto, podríamos actualizar la referencia, sin usar un molde explícito.

 Truck t = new Truck(); Vehicle v = t; 

También se denomina conversión de referencia de ampliación , básicamente porque a medida que sube en la jerarquía de tipos, el tipo se vuelve más general.

Podría usar un molde explícito aquí si quisiera, pero sería innecesario. Podemos ver que el objeto real referenciado por t y v es el mismo. Es, y siempre será un Truck .

Downcasting o estrechamiento de conversión de referencia

Ahora, teniendo una referencia de tipo Vechicle no podemos concluir “con seguridad” que realmente hace referencia a un Truck . Después de todo, también puede hacer referencia a alguna otra forma de vehículo. Por ejemplo

 Vehicle v = new Sedan(); //a light vehicle 

Si encuentra la referencia v en algún lugar de su código sin saber a qué objeto específico hace referencia, no puede argumentar de manera “segura” si apunta a un Truck o a un Sedan o cualquier otro tipo de vehículo.

El comstackdor sabe bien que no puede dar ninguna garantía sobre la verdadera naturaleza del objeto al que se hace referencia. Pero el progtwigdor, al leer el código, puede estar seguro de lo que está haciendo. Al igual que en el caso anterior, puede ver claramente que el Vehicle v hace referencia a un Sedan .

En esos casos, podemos hacer un abatimiento. Lo llamamos así porque estamos bajando la jerarquía de tipos. También llamamos a esto una reducción de la conversión de referencia . Podríamos decir

 Sedan s = (Sedan) v; 

Esto siempre requiere un lanzamiento explícito, porque el comstackdor no puede estar seguro de que esto sea seguro y por eso es como preguntarle al progtwigdor: “¿estás seguro de lo que estás haciendo?”. Si miente al comstackdor, obtendrá una ClassCastException en tiempo de ejecución, cuando se ejecute este código.

Otros tipos de reglas de subtipificación

Existen otras reglas de subtipado en Java. Por ejemplo, también hay un concepto llamado promoción numérica que coacciona automáticamente los números en las expresiones. Como en

 double d = 5 + 6.0; 

En este caso, una expresión compuesta por dos tipos diferentes, entero y doble, difunde / coacciona el entero a un doble antes de evaluar la expresión, lo que da como resultado un valor doble.

También puedes hacer upcasting primitivo y downcasting. Como en

 int a = 10; double b = a; //upcasting int c = (int) b; //downcasting 

En estos casos, se requiere un lanzamiento explícito cuando se puede perder información.

Algunas reglas de subtipado pueden no ser tan evidentes, como en el caso de las matrices. Por ejemplo, todas las matrices de referencia son subtipos de Object[] , pero las matrices primitivas no lo son.

Y en el caso de los generics, particularmente con el uso de comodines como super y extends , las cosas se complican aún más. Como en

 List a = new ArrayList<>(); List b = a; List c = new ArrayList<>(); List d = c; 

Donde el tipo de b es un subtipo del tipo de a . Y el tipo de d es un subtipo del tipo de c .

Y también el boxeo y el unboxing están sujetos a algunas reglas de casting (una vez más esto también es una forma de coerción en mi opinión).

Lo hiciste bien. Puede lanzar un objeto con éxito solo a su clase, algunas de sus clases principales o a alguna interfaz que implementen sus padres. Si lo fundió en algunas de las clases o interfaces principales, puede devolverlo al tipo original.

De lo contrario (aunque puede tenerlo en origen), se generará una ClassCastException en tiempo de ejecución.

El casting se usa generalmente para permitir almacenar diferentes cosas (de la misma interfaz o clase principal, por ejemplo, todos sus automóviles) en el mismo campo o una colección del mismo tipo (por ejemplo, un vehículo), para que pueda trabajar con ellos de la misma manera.

Si luego desea obtener acceso completo, puede devolverlos (por ejemplo, Vehículo a Camión)


En el ejemplo, estoy bastante seguro de que la última statement no es válida y el comentario es simplemente incorrecto.

Cuando haces un yeso desde un objeto Truck hasta un HeavyVehicle como ese:

 Truck truck = new Truck() HeavyVehicle hv = truck; 

El objeto sigue siendo un camión, pero solo tiene acceso a los métodos y campos de HeavyVehicle utilizando la referencia HeavyVehicle. Si vuelves a bajar a un camión, puedes usar nuevamente todos los métodos y campos del camión.

 Truck truck = new Truck() HeavyVehicle hv = truck; Truck anotherTruckReference = (Truck) hv; // Explicit Cast is needed here 

Si el objeto real que está realizando downcasting no es un camión, se lanzará una ClassCastException como en el siguiente ejemplo:

 HeavyVehicle hv = new HeavyVehicle(); Truck tr = (Truck) hv; // This code compiles but will throw a ClasscastException 

La excepción se produce porque el objeto real no es de la clase correcta, es un objeto de una superclase (Vehículo pesado)

La última línea de código se comstack y se ejecuta satisfactoriamente sin excepciones. Lo que hace es perfectamente legal.

  1. hV inicialmente se refiere a un objeto de tipo HeavyVehicle (llamemos a este objeto h1):

     static HeavyVehicle hV = new HeavyVehicle(); // hV now refers to h1. 
  2. Más tarde, hacemos que hV se refiera a un objeto diferente, de tipo Truck (llamemos a este objeto t1):

     hV = T; // hV now refers to t1. 
  3. Por último, hacemos que T se refiera a t1.

     T = (Truck) hV; // T now refers to t1. 

T ya se refería a t1, por lo que esta statement no cambió nada.

Si hv ya tiene asignada una instancia de una clase HeavyVehicle, que es una súper clase de la clase Truck, ¿cómo puede este campo ser lanzado en una subclase más específica llamada Truck que se extiende desde la clase HeavyVehicle?

Cuando llegamos a la última línea, hV ya no se refiere a una instancia de HeavyVehicle. Se refiere a una instancia de Camión. Lanzar una instancia de Camión para teclear Camión no es un problema.

Eso significa que el objeto solo se puede convertir como una superclase o la misma clase que la clase desde la que se creó la instancia. ¿Es correcto o estoy equivocado aquí?

Básicamente, sí, pero no confundas el objeto en sí con una variable que se refiera al objeto. Vea abajo.

¿La conversión explícita de alguna manera cambia el tipo de objeto real (no solo el tipo declarado), por lo que este objeto ya no es una instancia de la clase HeavyVehicle pero ahora se convierte en una instancia de la clase Truck?

No. Un objeto, una vez creado, nunca puede cambiar su tipo. No puede convertirse en una instancia de otra clase.

Para reiterar, nada cambió en la última línea. T se refiere a t1 antes de esa línea y se refiere a t1 después.

Entonces, ¿por qué es necesario el reparto explícito (Camión) en la última línea? Básicamente, estamos ayudando a ayudar al comstackdor.

Sabemos que en ese punto, hV se refiere a un objeto de tipo Truck, por lo que está bien asignar ese objeto de tipo Truck a la variable T. Pero el comstackdor no es lo suficientemente inteligente como para saberlo. El comstackdor quiere nuestra garantía de que cuando llegue a esa línea e intente realizar la tarea, encontrará una instancia de Truck esperando por ella.

El código anterior se comstackrá y ejecutará correctamente. Ahora cambie el código anterior y agregue la siguiente línea System.out.println (T.name);

Esto asegurará que no esté utilizando el objeto T después de lanzar el objeto hV como Camión.

Actualmente, en su código no está utilizando T después de la bajada, por lo que todo está bien y funcionando.

Esto se debe a que, al emitir explícitamente hV como Truck, el complier se queja, teniendo en cuenta que el progtwigdor arrojó el objeto y sabe qué objeto se ha enviado a qué.

Pero en tiempo de ejecución, JVM no puede justificar el lanzamiento y arroja ClassCastException “HeavyVehicle no puede ser lanzado a Truck”.

Para ayudar a ilustrar mejor algunos puntos anteriores, modifiqué el código en cuestión y le agregué más códigos con comentarios en línea (incluidos los resultados reales) de la siguiente manera:

 class Vehicle { String name; Vehicle() { name = "Vehicle"; } } class HeavyVehicle extends Vehicle { HeavyVehicle() { name = "HeavyVehicle"; } } class Truck extends HeavyVehicle { Truck() { name = "Truck"; } } class LightVehicle extends Vehicle { LightVehicle() { name = "LightVehicle"; } } public class InstanceOfExample { static boolean result; static HeavyVehicle hV = new HeavyVehicle(); static Truck T = new Truck(); static HeavyVehicle hv2 = null; public static void main(String[] args) { result = hV instanceof HeavyVehicle; System.out.print("hV is a HeavyVehicle: " + result + "\n"); // true result = T instanceof HeavyVehicle; System.out.print("T is a HeavyVehicle: " + result + "\n"); // true // But the following is in error. // T = hV; // error - HeavyVehicle cannot be converted to Truck because all hV's are not trucks. result = hV instanceof Truck; System.out.print("hV is a Truck: " + result + "\n"); // false hV = T; // Sucessful Cast form child to parent. result = hV instanceof Truck; // This only means that hV now points to a Truck object. System.out.print("hV is a Truck: " + result + "\n"); // true T = (Truck) hV; // Sucessful Explicit Cast form parent to child. Now T points to both HeavyVehicle and Truck. // And also hV points to both Truck and HeavyVehicle. Check the following codes and results. result = hV instanceof Truck; System.out.print("hV is a Truck: " + result + "\n"); // true result = hV instanceof HeavyVehicle; System.out.print("hV is a HeavyVehicle: " + result + "\n"); // true result = hV instanceof HeavyVehicle; System.out.print("hV is a HeavyVehicle: " + result + "\n"); // true result = hv2 instanceof HeavyVehicle; System.out.print("hv2 is a HeavyVehicle: " + result + "\n"); // false } }