Saltar a contenido

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.

polimorfismo

El análisis del problema puede darnos un diagrama de clases como el que sigue

polimorfismo

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....");
    }
}
Cada uno de los animales habla su propio idioma al sobrescribir el método 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();
    }
Cada uno de los personajes hablará en su idioma al llamar a 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();
muestra

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();
        }
    }
polimorfismo

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:

Polimorfismo

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";
}
Es una característica que hay que activar en java 17 pero que ya está disponible en la versión 21