Polimorfismo¶
El Polimorfismo es uno de los 4 pilares de la programación orientada a objetos (POO) junto con la Abstracción, Encapsulación y Herencia. Para entender que es el polimorfismo es muy importante que tengáis bastante claro el concepto de la Herencia.
Polimorfismo significa "que tiene muchas formas", es la capacidad que tienen los objetos de una clase en ofrecer respuesta distinta e independiente en función de los parámetros (diferentes implementaciones) utilizados durante su invocación.
Veamos un ejemplo para entender de forma más clara cómo funciona el polimorfismo.
Queremos crear un juego en la que tenemos animales de diferentes especies y que puedan hablar cada una en su propio lenguaje de su especie.
El análisis del problema puede darnos un diagrama de clases como el que sigue
Cada animal tiene su nombre y tenemos un método hablar() que cada especie tiene que implementar.
Crearemos unas clases que heredan de la clase padre Animal
:
class Animal {
private String nombre;
public Animal(String nombre) {
this.nombre = nombre;
}
public void hablar() {
System.out.println( nombre+ " dice: ....";
}
}
class Gato extends Animal {
public Gato(String nombre) {
super(nombre);
}
@Override
public void hablar() {
System.out.println( nombre+ " dice: Miaaauuuuuu....");
}
}
class Perro extends Animal {
public Perro(String nombre) {
super(nombre);
}
@Override
public void hablar() {
System.out.println( nombre+ " dice: Guuuaaaaaaauuuuu.....");
}
}
class Persona extends Animal {
public Persona(String nombre) {
super(nombre);
}
@Override
public void hablar() {
System.out.println( nombre+ " dice: Blablablabla....");
}
}
hablar()
En nuestro fantástico juego, queremos que los animales de la habitación tengan una conversación, el código puede ser algo parecido a los siguiente.
Gato gato1=new Gato("Garfiel");
Perro perro1=new Perro("Laika");
Persona persona1=new Persona("Pirico")
public void conversar() {
gato1.hablar();
perro1.hablar();
persona1.hablar();
}
conversar()
.
Garfield dice: Miaaauuuuuu....
Laika dice: Guuuaaaaaaauuuuu.....
Perico dice: Blablablabla....
Pero en nuestro juego no sabemos ni cuantos animales ni el tipo de animal que pueden haber en la habitación, todo depende del desarrollo del juego por parte del jugador. Pero tenemos que hacer que todos los animales de la habitación hablen.
La potencia del polimorfismo nos permite tener en una variable del tipo de la clase padre, en nuestro caso Animal
una instancia de las clases hijas y cuando llamamos al método hablar()
llamará al método de la clase hija.
Animal animal=new Gato("Garfiel");
animal.hablar();
Garfield dice: Miaaauuuuuu....
Esto nos permite una solución más abierta manteniendo una lista de tipo Animal, donde guardamos los diferentes animales y recorremos para que hablen todos los animales, cada uno en su idioma.
//creamos la lista de tipo Animal
ArrayList<Animal> animales=new ArrayList<>();
//añadimos animales de los tipos descendientes
animales.add(new Gato("Garfiel"));
animales.add(Perro("Laika"));
animales.add(new Persona("Pirico"));
public void conversar() {
for (Animal animal:animales) {
//llamará al método correspondiente del tipo de la instancia
animal.hablar();
}
}
De esta forma, podríamos añadir en un futuro nuevos tipos de animal y que no afectara al código del método conversar()
.
animales.add(new Raton("Mickey"));
Resumen
La sobrescritura de métodos y las conversiones entre clases de la jerarquía sientan las bases para el polimorfismo. Es necesario entender bien estos conceptos para comprender el polimorfismo. Este se puede definir como la cualidad que tienen los objetos para responder de distinto modo a un mismo mensaje.
El programa tiene que cumplir
- Los métodos deben estar declarados (métodos abstractos) y a veces también pueden estar implementados (métodos no abstractos) en la clase base.
- Los métodos deben estar redefinidos en las clases derivadas.
- Los objetos deben ser manipulados utilizando referencias a la clase base.
Upcasting, Downcasting¶
Una variable puede contener una referencia a un objeto cuya clase es descendiente de la clase de la variable. Ejemplo:
//La variable a es una referencia a un objeto Perro que es descendiente de Animal.
Animal a = new Perro();
Un descendiente de una clase es un hijo de esa clase, o un hijo de un hijo de esa clase, y así sucesivamente. Los hermanos no son descendientes entre sí.
NO podemos asignar un objeto de referencia de padre a una variable de clase hijo (Perro p = new Animal()). Si queremos convertir un padre en hijo, la variable tiene que ser creada de tipo hijo. Si queremos convertir un hijo en padre tendremos que hacer un Upcasting, y al revés tendríamos un Downcasting:
Ejemplo
//NO SE PUEDE HACER
Perro p = new Animal(); //no compila
Perro p = (Perro) new Animal(); // compila pero da error de ejecución
//DOWNCASTING, convertir padre en hijo
Animal a = new Perro();
Perro pe = (Perro)a;
//UPCASTING, convertir hijo en padre
Animal a = (Animal) new Perro();
El operador instanceof
¶
Las operaciones entre clases y en particular el downcasting requieren que las clases sean de tipos
compatibles. Para asegurarnos de ello podemos utilizar el operador instanceof
. Por otro lado, podemos tener la necesidad de tomar decisiones en función del tipo del objeto
instanceof devuelve true si el objeto es instancia de la clase y false en caso contrario. La sintaxis es:
Objecto instanceof Clase
Animal animal=new Gato("ConBotas");
if(animal instanceof Gato)
System.out.println("soy un gato");
El nuevo switch de java 17 nos permite tomar decisiones en función del tipo de objeto
String queSoy=switch(animal){
case Gato g -> "soy un gato";
case Perro p -> "soy un perro";
case Persona p -> "soy una persona";
default -> "no se que quien soy";
}