"¡Hola, amigo! El tema de la lección de hoy es la ampliación y reducción de las conversiones de tipo. Aprendiste sobre la ampliación y reducción de tipos primitivos hace mucho tiempo. En el nivel 10. Hoy vamos a hablar sobre cómo funciona para los tipos de referencia, es decir, instancias de clases".

De hecho, todo es bastante simple. Imagine la cadena de herencia de una clase: la clase, su padre, el padre del padre, etc., hasta la clase Objeto. Debido a que una clase contiene todos los métodos miembros de la clase que hereda , una instancia de la clase se puede guardar en una variable cuyo tipo sea el de cualquiera de sus padres.

Aquí hay un ejemplo:

Código Descripción
class Animal
{
public void doAnimalActions();
}class Cat extends Animal
{
public void doCatActions();
}class Tiger extends Cat
{
public void doTigerActions();
}
Aquí tenemos tres declaraciones de clase: Animal, Cat y Tiger. Gato hereda Animal. Y Tiger hereda Cat.
public static void main(String[] args)
{
Tiger tiger = new Tiger();
Cat cat = new Tiger();
Animal animal = new Tiger();
Object obj = new Tiger();
}
Un objeto Tiger siempre se puede asignar a una variable cuyo tipo sea el de uno de sus ancestros. Para la clase Tigre, estos son Gato, Animal y Objeto.

Ahora echemos un vistazo a las conversiones de ampliación y reducción.

Si una operación de asignación hace que avancemos en la cadena de herencia (hacia la clase Object), estamos tratando con una conversión ampliada (también conocida como upcasting). Si nos movemos hacia abajo en la cadena hacia el tipo de objeto, entonces es una conversión de reducción (también conocida como reducción).

Avanzar en la cadena de herencia se denomina ampliación, porque conduce a un tipo más general. Sin embargo, al hacerlo, perdemos la capacidad de invocar los métodos agregados a la clase a través de la herencia.

Código Descripción
public static void main(String[] args)
{
Object obj = new Tiger();
Animal animal = (Animal) obj;
Cat cat = (Cat) obj;
Tiger tiger = (Tiger) animal;
Tiger tiger2 = (Tiger) cat;
}
Al restringir el tipo, debe usar un operador de conversión de tipo, es decir, realizamos una conversión explícita.

Esto hace que la máquina Java compruebe si el objeto realmente hereda el tipo al que queremos convertirlo.

Esta pequeña innovación produjo una reducción múltiple en la cantidad de errores de conversión de tipos y aumentó significativamente la estabilidad de los programas Java.

Código Descripción
public static void main(String[] args)
{
Object obj = new Tiger();
if (obj instanceof Cat)
{
Cat cat = (Cat) obj;
cat.doCatActions();
}}
Mejor aún, use una  instancia de verificación
public static void main(String[] args)
{
Animal animal = new Tiger();
doAllAction(animal);

Animal animal2 = new Cat();
doAllAction(animal2);

Animal animal3 = new Animal();
doAllAction(animal3);
}

public static void doAllAction(Animal animal)
{
if (animal instanceof Tiger)
{
Tiger tiger = (Tiger) animal;
tiger.doTigerActions();
}

if (animal instanceof Cat)
{
Cat cat = (Cat) animal;
cat.doCatActions();
}

animal.doAnimalActions();
}
Y he aquí por qué. Fíjate en el ejemplo de la izquierda.

Nosotros (nuestro código) no siempre sabemos con qué tipo de objeto estamos trabajando. Puede ser un objeto del mismo tipo que la variable (Animal), o cualquier tipo descendiente (Gato, Tigre).

Considere el método doAllAction. Funciona correctamente, independientemente del tipo de objeto pasado.

En otras palabras, funciona correctamente para los tres tipos: Animal, Gato y Tigre.

public static void main(String[] args)
{
Cat cat = new Tiger();
Animal animal = cat;
Object obj = cat;
}
Aquí tenemos tres operaciones de asignación. Todos ellos son ejemplos de conversiones ampliadas.

El operador de conversión de tipos no es necesario aquí, porque no es necesaria ninguna verificación. Una referencia de objeto siempre se puede almacenar en una variable cuyo tipo es uno de sus ancestros.

"Oh, el penúltimo ejemplo dejó todo claro: por qué se necesita la verificación y por qué se necesita la conversión de tipos".

"Eso espero. Quiero llamar su atención sobre este hecho:"

¡Nada de esto hace que un objeto cambie de ninguna manera! Lo único que cambia es la cantidad de métodos disponibles para llamar en una variable de referencia particular.

Por ejemplo, una variable Cat le permite llamar a los métodos doAnimalActions y doCatActions. No sabe nada sobre el método doTigerActions, incluso si apunta a un objeto Tiger.

"Sí, lo entiendo. Fue más fácil de lo que pensé que sería".