1. Encasillamiento

Las variables que almacenan tipos de referencia (clases) también se pueden convertir a diferentes tipos. Pero esto solo funciona dentro de una jerarquía de un solo tipo. Veamos un ejemplo sencillo. Supongamos que tenemos la siguiente jerarquía de clases, en la que las clases de abajo heredan las clases de arriba.

encasillamiento

El encasillamiento de tipos de referencia, así como de tipos primitivos, también se clasifica como de ampliación y de estrechamiento.

Vemos que la clase Gato hereda la clase Mascota, y la clase Mascota, a su vez, hereda la clase Animal.

Si escribimos código como este:

Animal kitten = new Cat();

Esta es una conversión de tipo de ampliación . También se denomina conversión implícita. Hemos ampliado la referencia al gato para que ahora se refiera a un objeto Gato . Con una conversión de tipo como esta, no podremos usar la referencia gatito para llamar a métodos que están presentes en la clase Gato pero ausentes en la clase Animal .

Una conversión de estrechamiento (o conversión explícita) ocurre en la dirección opuesta:

Cat cat = (Cat) kitten;

Indicamos explícitamente que queremos convertir la referencia almacenada en la variable gatito (cuyo tipo es Animal ) al tipo Gato .



2. Comprobar el tipo de un objeto

Pero hay que tener mucho cuidado aquí. Si haces esto:

Animal beast = new Cat();
Wolf grayWolf = (Wolf) beast;

El compilador permitirá este código, ¡pero habrá un error cuando se ejecute el programa! La JVM lanzará una excepción:

Exception in thread "main" java.lang.ClassCastException: Cat cannot be cast to a Wolf

Las referencias a un objeto Gato solo se pueden almacenar en variables cuyo tipo sea un ancestro de la clase Gato: Mascota, Animal u Objeto.

¿Porqué es eso?

El punto relevante aquí es que una referencia de objeto se usa para referirse a los métodos y variables de ese objeto . Y no habrá ningún problema si usamos una variable Animal para almacenar una referencia a un objeto Cat: el tipo Cat siempre tiene variables y métodos del tipo Animal , ¡los heredó!

Pero si la JVM nos permitiera almacenar una referencia a un objeto Cat en una variable Wolf, entonces podríamos tener una situación en la que podríamos intentar usar la variable grayWolf para llamar a un método que no existe en el objeto Cat almacenado en esa variable. . Es por eso que este arreglo no está permitido.

Java tiene un operador especial instanceofque le permite verificar si un objeto es de cierto tipo y, por lo tanto, puede almacenarse en una variable de cierto tipo. Parece bastante simple:

variable instanceof Type

Ejemplo:

Animal beast = new Cat();
if (beast instanceof Wolf)
{
   Wolf grayWolf = (Wolf) beast;
}

Este código no causará errores, incluso en tiempo de ejecución.

Aquí hay algunos ejemplos más que ilustran la situación:

Conversión de tipo de ensanchamiento Descripción
Cow cow = new Whale();

Esta es una conversión de ampliación clásica: no se requiere ningún operador de conversión de tipos. Ahora solo los métodos definidos en la Cowclase pueden llamarse en el Whaleobjeto.

En la cowvariable , el compilador solo le permitirá llamar a los métodos que Cowtiene su tipo (la clase).

Conversión de tipo de estrechamiento
Cow cow = new Whale();
if (cow instanceof Whale)
{
   Whale whale = (Whale) cow;
}
Conversión de restricción clásica: debe agregar una verificación de tipo y un operador de conversión.
La Cow cowvariable almacena una referencia a un Whaleobjeto.
Verificamos que este sea el caso , y luego realizamos una conversión de tipo (estrechamiento). O como también se le llama:
un tipo de elenco
.

Cow cow = new Cow();
Whale whale = (Whale) cow; // Exception
Puede limitar un tipo de referencia sin comprobar el tipo del objeto.
Si la cowvariable hace referencia a un objeto que no es un , se generará Whaleun .InvalidClassCastException


3. Llamar al método original: la superpalabra clave

Cuando anulamos el método de una clase principal, a veces, en lugar de reemplazarlo con el nuestro, solo queremos complementarlo ligeramente.

Sería genial si pudiéramos usar el método de la clase principal en nuestro método y luego ejecutar algo de nuestro propio código. O tal vez ejecutar primero nuestro propio código y luego llamar al método de la clase principal.

Y Java nos permite hacer precisamente eso. Para llamar a un método de la clase padre, haga esto:

super.method(arguments);

Ejemplos:

class PeaceTime
{
   public double getPi()
   {
      return 3.14;
   }
}

class WarTime extends PeaceTime
{
   public double getPi()
   {
      return super.getPi()*2;  // 3.14*2
   }
}

¡En tiempos de guerra, el valor de Pipuede ser mayor que 6! Por supuesto, estamos bromeando, pero este ejemplo demuestra cómo puede funcionar todo esto.

Aquí hay un par de ejemplos más para aclarar un poco las cosas:

Código Descripción
class Cow
{
   public void printAll()
   {
      printColor();
      printName();
   }

   public void printColor()
   {
      System.out.println("I'm a white whale");
   }

   public void printName()
   {
      System.out.println("I'm a cow");
   }
}

class Whale extends Cow
{
   public void printName()
   {
      System.out.print("This is incorrect: ");
      super.printName();
      System.out.println("I'm a whale");
   }
}
Cowy Whaleclases
public static void main(String[] args)
{
   Whale whale = new Whale();
   whale.printAll();
}
La salida de pantalla será:
I'm a white whale
This is incorrect: I'm a cow
I'm a whale

Esto es algo difícil. Honestamente, es una de las cosas más difíciles en OOP . Dicho esto, necesitas saberlo y entenderlo.