"Amigo, ¿te gustan las ballenas?"

"¿Ballenas? No, nunca he oído hablar de ellas".

"Es como una vaca, solo que más grande y nada. Por cierto, las ballenas provienen de las vacas. Uh, o al menos comparten un ancestro común. No importa".

Polimorfismo y anulación - 1

"Escucha. Quiero hablarte de otra herramienta muy poderosa de OOP: el polimorfismo . Tiene cuatro características".

1) Anulación de método.

Imagina que has escrito una clase de "vaca" para un juego. Tiene muchas variables miembro y métodos. Los objetos de esta clase pueden hacer varias cosas: caminar, comer, dormir. Las vacas también suenan una campana cuando caminan. Digamos que ha implementado todo en la clase hasta el más mínimo detalle.

Polimorfismo y anulación - 2

Entonces, de repente, el cliente dice que quiere lanzar un nuevo nivel del juego, donde todas las acciones tienen lugar en el mar y el personaje principal es una ballena.

Comenzaste a diseñar la clase Ballena y te diste cuenta de que es solo ligeramente diferente a la clase Vaca. Ambas clases usan una lógica muy similar y usted decide usar la herencia.

La clase Vaca es ideal para ser la clase principal: ya tiene todas las variables y métodos necesarios. Todo lo que necesitas hacer es agregar la habilidad de nadar de la ballena. Pero hay un problema: tu ballena tiene patas, cuernos y una campana. Después de todo, la clase Cow implementa esta funcionalidad. ¿Qué puedes hacer?

Polimorfismo y anulación - 3

La anulación de métodos viene al rescate. Si heredamos un método que no hace exactamente lo que necesitamos en nuestra nueva clase, podemos reemplazar el método por otro.

Polimorfismo y anulación - 4

¿Cómo se hace esto? En nuestra clase descendiente, declaramos el método que queremos cambiar (con la misma firma de método que en la clase principal) . Luego escribimos un nuevo código para el método. Eso es todo. Es como si el antiguo método de la clase principal no existiera.

Así es como funciona:

Código Descripción
class Cow
{
public void printColor()
{
System.out.println("I'm white");
}
public void printName()
{
System.out.println("I'm a cow");
}
}class Whale extends Cow
{
public void printName()
{
System.out.println("I'm a whale");
}
}
Aquí definimos dos clases:  Cow y  WhaleWhalehereda  Cow_

La  Whale clase anula el  printName();método.

public static void main(String[] args)
{
Cow cow = new Cow();
cow.printName();
}
Este código muestra " Soy una vaca " en la pantalla.
public static void main(String[] args)
{
Whale whale = new Whale();
whale.printName();
}
Este código muestra " Soy una ballena " en la pantalla

Después de heredar Cowy anular printName, la Whaleclase en realidad tiene los siguientes datos y métodos:

Código Descripción
class Whale
{
public void printColor()
{
System.out.println("I'm white");
}
public void printName()
{
System.out.println("I'm a whale");
}
}
No sabemos nada sobre ningún método antiguo.

"Honestamente, eso es lo que esperaba".

2) Pero eso no es todo.

"Supongamos que la  Cow clase tiene un  printAllmétodo que llama a los otros dos métodos. Entonces el código funcionaría así:"

La pantalla mostrará:
Soy blanco
Soy una ballena

Código Descripción
class Cow
{
public void printAll()
{
printColor();
printName();
}
public void printColor()
{
System.out.println("I'm white");
}
public void printName()
{
System.out.println("I'm a cow");
}
}

class Whale extends Cow
{
public void printName()
{
System.out.println("I'm a whale");
}
}
public static void main(String[] args)
{
Whale whale = new Whale();
whale.printAll();
}
La pantalla mostrará:
Soy blanco
Soy una ballena

Tenga en cuenta que cuando se llama al método printAll () de la clase Cow en un objeto Whale, se utilizará el método printName() de Whale, no el de Cow.

Lo importante no es la clase en la que está escrito el método, sino el tipo (clase) del objeto en el que se llama al método.

"Veo."

"Solo puede heredar y anular métodos no estáticos. Los métodos estáticos no se heredan y, por lo tanto, no se pueden anular".

Así es como se ve la clase Whale después de aplicar la herencia y anular los métodos:

Código Descripción
class Whale
{
public void printAll()
{
printColor();
printName();
}
public void printColor()
{
System.out.println("I'm white");
}
public void printName()
{
System.out.println("I'm a whale");
}
}
Así es como se ve la clase Whale después de aplicar la herencia y anular el método. No sabemos nada sobre ningún printNamemétodo antiguo.

3) Fundición de tipos.

Aquí hay un punto aún más interesante. Debido a que una clase hereda todos los métodos y datos de su clase principal, un objeto de esta clase puede ser referenciado por variables de la clase principal (y el principal del principal, etc., hasta la clase Object). Considere este ejemplo:

Código Descripción
public static void main(String[] args)
{
Whale whale = new Whale();
whale.printColor();
}
La pantalla mostrará:
Soy blanco.
public static void main(String[] args)
{
Cow cow = new Whale();
cow.printColor();
}
La pantalla mostrará:
Soy blanco.
public static void main(String[] args)
{
Object o = new Whale();
System.out.println(o.toString());
}
La pantalla mostrará:
Whale@da435a.
El método toString() se hereda de la clase Object.

"Buen material. Pero, ¿por qué necesitarías esto?"

"Es una característica valiosa. Más tarde comprenderá que es muy, muy valiosa".

4) Enlace tardío (despacho dinámico).

Esto es lo que parece:

Código Descripción
public static void main(String[] args)
{
Whale whale = new Whale();
whale.printName();
}
La pantalla mostrará:
Soy una ballena.
public static void main(String[] args)
{
Cow cow = new Whale();
cow.printName();
}
La pantalla mostrará:
Soy una ballena.

Tenga en cuenta que no es el tipo de la variable lo que determina qué método específico de printName llamamos (el de la clase Vaca o Ballena), sino el tipo de objeto al que hace referencia la variable.

La variable Cow almacena una referencia a un objeto Whale y se llamará al método printName definido en la clase Whale .

"Bueno, no agregaron eso en aras de la claridad".

"Sí, no es tan obvio. Recuerda esta regla importante:"

El conjunto de métodos que puede llamar en una variable está determinado por el tipo de variable. Pero qué método/implementación específico se llama está determinado por el tipo/clase del objeto al que hace referencia la variable.

"Voy a tratar de."

"Te encontrarás con esto constantemente, por lo que lo entenderás rápidamente y nunca lo olvidarás".

5) Fundición de tipos.

La conversión funciona de manera diferente para los tipos de referencia, es decir, las clases, que para los tipos primitivos. Sin embargo, las conversiones de ampliación y reducción también se aplican a los tipos de referencia. Considere este ejemplo:

Ampliación de la conversión Descripción
Cow cow = new Whale();

Una conversión de ampliación clásica. Ahora solo puede llamar a métodos definidos en la clase Vaca en el objeto Ballena.

El compilador le permitirá usar la variable vaca solo para llamar a los métodos definidos por el tipo Vaca.

Conversión de reducción Descripción
Cow cow = new Whale();
if (cow instanceof Whale)
{
Whale whale = (Whale) cow;
}
Una conversión de estrechamiento clásica con una verificación de tipo. La variable vaca de tipo Vaca almacena una referencia a un objeto Ballena.
Verificamos que este sea el caso , y luego realizamos la conversión de tipo (ampliación). Esto también se llama conversión de tipos .
Cow cow = new Cow();
Whale whale = (Whale) cow; //exception
También puede realizar una conversión de restricción de un tipo de referencia sin verificar el tipo del objeto.
En este caso, si la variable vaca apunta a algo que no sea un objeto Ballena, se generará una excepción (InvalidClassCastException).

6) Y ahora algo sabroso. Llamando al método original.

A veces, al anular un método heredado, no desea reemplazarlo por completo. A veces solo quieres agregarle un poco.

En este caso, realmente desea que el código del nuevo método llame al mismo método, pero en la clase base. Y Java te permite hacer esto. Así es como se hace:  super.method().

Aquí hay unos ejemplos:

Código Descripción
class Cow
{
public void printAll()
{
printColor();
printName();
}
public void printColor()
{
System.out.println("I'm white");
}
public void printName()
{
System.out.println("I'm a cow");
}
}

class Whale extends Cow
{
public void printName()
{
System.out.print("This is false: ");
super.printName();

System.out.println("I'm a whale");
}
}
public static void main(String[] args)
{
Whale whale = new Whale();
whale.printAll();
}
La pantalla mostrará:
Soy blanco
Esto es falso: Soy una vaca
Soy una ballena

"Hmm. Bueno, eso fue una lección. Mis oídos de robot casi se derriten".

"Sí, esto no es algo simple. Es uno de los materiales más difíciles que encontrará. El profesor prometió proporcionar enlaces a materiales de otros autores, de modo que si todavía no entiende algo, puede completar el brechas."