Herencia y Polimorfismo en Java

COMPARTIR EN REDES SOCIALES

En esta entrada vamos a dar un profundo repaso a la Herencia y Polimorfismo en Java.

Primero definamos qué es la Herencia y qué es el Polimorfismo.

¿ Qué es la Herencia ?

La herencia es una de las propiedades de los lenguajes de programación orientados a Objetos que nos permiten crear clases derivadas a partir de una clase que llamamos Padre. La clase Hija hereda los atributos y métodos de su clase Padre, veamos un ejemplo teórico:

Queremos implementar los animales de un zoológico utilizando herencia en Java. Si analizamos el problema nos damos cuenta de que todo Animal del Zoológico tendrá unos atributos comunes, como puede ser el nombre y el color del animal. Y habrá animales que podrán volar, otros podrán nadar, todos podrán comer pero cada uno comerá de una manera determinada, etc… Lo ideal sería crear una clase llamada Animal que tenga todos aquellos atributos que luego vayan a utilizar nuestras clases Hijas. Implementemos la clase Animal y la clase Pájaro:

Nuestra clase Animal

public abstract class Animal {
    
    private String nombre;
    private String color;

    public Animal(String nombre, String color) {
        this.nombre = nombre;
        this.color = color;
    }

    public String getNombre() {
        return nombre;
    }

    public void setNombre(String nombre) {
        this.nombre = nombre;
    }

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }
    
    abstract void comer();
}

Nuestra clase Pajaro

public class Pajaro extends Animal{
    
    public Pajaro(String nombre, String color){
        super(nombre, color);
    }
    
    public void comer(){
        System.out.println("Soy un pájaro y como alpiste");
    }
    
    public void volar(){
        System.out.println("Soy un pajaro y puedo volar");
    }
    
}

Expliquemos el código:

Nuestra clase Animal es abstracta porque no tiene sentido instanciar un Animal, tiene sentido instanciar las clases Hijas de Animal, como Pajaro, pero no Animal en sí, esto lo conseguimos especificando la clase como «abstract class». En nuestra clase Animal creamos todos los atributos de instancia comunes a todos los animales, en este caso «nombre» y «color». Creamos también el constructor parametrizado, los getters y setters de cada atributo de la instancia y un método abstracto llamado «comer». Crear un método abstracto aquí obligará a que toda clase que herede de Animal tenga que implementar el método «comer».

Con respecto a la clase pájaro fijaos que en la definición de la clase utilizamos «extends Animal», con esta expresión estamos diciendo que nuestra clase Pajaro hereda de Animal y que por lo tanto, Pajaro es un Animal, heredará el comportamiento de la clase Animal. Creamos un constructor parametrizado de Pajaro que llama al constructor de Animal para establecer los valores comunes «nombre» y «color», esto lo hacemos mediante «super(nombre, color);». Por último implementamos el método obligatorio «comer» e implementamos uno opcional «volar».

¿ Cómo probamos nuestras clases ?

public class HerenciaBlog {

    public static void main(String[] args) {
       Pajaro miPajaro = new Pajaro("Pepito", "Amarillo");
       
       miPajaro.comer();
       miPajaro.volar();
    }
}

La salida de nuestro programa

Soy un pájaro y como alpiste
Soy un pajaro y puedo volar

Entendiendo la palabra abstract

En el párrafo anterior hemos comentado que si definimos una clase como «abstract» no se puede instanciar y que esto a veces tiene sentido, como en el ejemplo de Zoológico mediante el cuál vamos a trabajar en este artículo. Comprobemos si realmente podemos instancia un animal:

Podemos instanciar cualquier clase que no sea «abstract», pero no se puede instanciar ninguna clase abstract.

El segundo uso que le hemos dado a «abstract» es para los métodos:

public abstract class Animal {
    
    private String nombre;
    private String color;
    ....
    ....
    abstract void comer();
}

Esto nos obliga a implementar el método comer en toda clase Hija que herede de Animal, ¿ qué ocurre si no lo implementamos?:

Java nos dice que nuestra clase Pajaro si hereda de Animal debe sí o sí implementar el método «comer».

Comportamiento heredado en las clases Hijas

Antes hemos comentado que las clases hijas heredan el comportamiento de las clases Padres, esto quiere decir que las clases Hijas ven todo método o atributo «public» o «protected» de su clase Padre. Veamos un ejemplo, vamos a intentar llamar a los métodos get de la clase Animal a través de un Pajaro:

public class HerenciaBlog {

    public static void main(String[] args) {
       Pajaro miPajaro = new Pajaro("Pepito", "Amarillo");
       
       miPajaro.comer();
       miPajaro.volar();
       
       System.out.println("Me llamo: " + miPajaro.getNombre());
       System.out.println("Soy de color: " + miPajaro.getColor());
    }
}

La salida de nuestro programa

Soy un pájaro y como alpiste
Soy un pajaro y puedo volar
Me llamo: Pepito
Soy de color: Amarillo

Como podéis ver los métodos públicos definidos en Animal son heredados por pájaro, y por lo tanto, podemos utilizarlos a través de la instancia de Pajaro.

¿ Se heredan los atributos ?

Se hereda todo atributo cuyo modificador de acceso sea «public» o «protected», ya deberíais saber que utilizar «public» en un atributo está prohibido porque va en contra de las buenas prácticas de Java, por lo tanto, podemos utilizar protected si necesitamos acceder al atributo directamente, veamos un ejemplo:

public abstract class Animal {
    
    protected String nombre;
    private String color;

    public Animal(String nombre, String color) {
        this.nombre = nombre;
        this.color = color;
    }

    public String getNombre() {
        return nombre;
    }

    public void setNombre(String nombre) {
        this.nombre = nombre;
    }

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }
    
    abstract void comer();
}
public class Pajaro extends Animal{
    
    public Pajaro(String nombre, String color){
        super(nombre, color);
    }
    
    public void comer(){
        System.out.println("Soy un pájaro y como alpiste");
    }
    
    public void volar(){
        System.out.println("Soy " + this.nombre + ", un pajaro y puedo volar");
    }
    
}

Fijaos como en la línea 3 de Animal declaramos nuestro atributo «nombre» como «protected», y en la línea 12 de Pajaro accedemos directamente al atributo sin que sea necesario utilizar su getter público.

Si estuviese en «private» no podríamos acceder directamente al atributo y tendríamos que utilizar algún método público para acceder a dicho atributo, como el método «getNombre».

La sobreescritura de métodos

Las clases hijas puede sobrescribir métodos de sus clases padres, veamos un ejemplo en el que vamos a poner el método comer como método no abstracto, es decir, como método normal:

public abstract class Animal {
    
    protected String nombre;
    private String color;

    public Animal(String nombre, String color) {
        this.nombre = nombre;
        this.color = color;
    }

    public String getNombre() {
        return nombre;
    }

    public void setNombre(String nombre) {
        this.nombre = nombre;
    }

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }
    
    public void comer(){
        System.out.println("Soy un animal y como!");
    }
}

Nuestra clase Pajaro

public class Pajaro extends Animal{
    
    public Pajaro(String nombre, String color){
        super(nombre, color);
    }
    
    public void comer(){
        System.out.println("Soy un pájaro y como alpiste");
    }
    
    public void volar(){
        System.out.println("Soy " + this.nombre + ", un pajaro y puedo volar");
    }
    
}

Fijaos como hemos sobrescrito el método comer, tenemos una implementación del mismo en Animal y otra en Pajaro.

¿ Si sobrescribimos un método a qué método se llama al de la clase Padre o al de la clase Hija?

Siempre se llamará al método de la clase Hija si esta implementa el método, por el contrario, se llamaría al de la clase Padre, es decir, aquí existirían 2 alternativas:

  1. El método comer está implementado en la clase Pajaro y en la clase Animal. Se llama al método comer de la clase Hija.
  2. El método comer está implementado en la clase Animal pero no en Pajaro. Se llama al método comer de la clase Padre.

Esto se puede hacer en Java gracias al concepto de «Ligadura tardía», es decir, un mecanismo que permite que no se establezca el método al cual se llamará hasta que el programa esté en ejecución.

Cuando el programa esté en ejecución se buscará donde está la implementación del método buscado más cercano a la instancia desde la que se ejecuta el método.

El polimorfismo en Java

Polimorfismo significa muchas formas y en Java se utiliza para tratar a una clase Hija como su clase Padre llamando a métodos que están definidos en su clase Padre y también en las clases Hijas dejando que Java llame por nosotros al método correcto. Veamos un ejemplo, pero para ello creemos primero una clase llamada Leon:

public class Leon extends Animal {
    
    public Leon(String nombre, String color){
        super(nombre, color);
    }
    
    public void comer(){
        System.out.println("Soy un león y como carne");
    }
    
    public void correr(){
        System.out.println("Soy un león y estoy corriendo");
    }
}

El pilar fundamental del polimorfismo es que un Pajaro y un Leon son Animal, por lo tanto podemos hacer lo siguiente:

 public static void main(String[] args) {
       Animal miPajaro = new Pajaro("Pepito", "Amarillo");
       Animal miLeon = new Leon("Leoncito", "Amarillo");
       
       miPajaro.comer();
       miLeon.comer();
}

Fijaos que en la parte de la declaración (parte izquierda de la asignación) declaramos un objeto de tipo Animal, pero en la parte derecha instanciamos las clases concretas. Esto nos permite tratar tanto al Pajaro como al Leon como un Animal. ¿ Y para que sirve esto ? Bien, es una buena pregunta, veamos el ejemplo con un ArrayList<Animal>, metamos nuestros animales dentro de un ArrayList<Animal>:

public static void main(String[] args) {
        
       Animal miPajaro = new Pajaro("Pepito", "Amarillo");
       Animal miLeon = new Leon("Leoncito", "Amarillo");

       List<Animal> animales = Arrays.asList(miPajaro, miLeon);

       
       for (Animal a: animales){
           a.comer();
       }
    }

Fijaos que esto nos permite tener en una colección a todos nuestros animales aunque sean de tipo diferente y ¿Cuál creéis que es la salida?

Soy un pájaro y como alpiste
Soy un león y como carne

Fijaos que se llama al método comer de la instancia correcta debido al concepto que hemos visto anteriormente de «ligadura tardía», pero tenemos un problema, como son objetos de tipo Animal perdemos la posibilidad de llamar a sus métodos, ya que en Leon tenemos «correr» y en Pajaro tenemos «volar». El problema es que tenemos animales, y por lo tanto, los animales no pueden «correr» ni «volar». No pasa nada, el operador «instanceof» y el casteo al rescate:

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class HerenciaBlog {

    public static void main(String[] args) {
        
       Animal miPajaro = new Pajaro("Pepito", "Amarillo");
       Animal miLeon = new Leon("Leoncito", "Amarillo");

       List<Animal> animales = Arrays.asList(miPajaro, miLeon);

       
       for (Animal a: animales){
           a.comer();
           
           if (a instanceof Leon){
               Leon l = (Leon)a;
               l.correr();
           }else if (a instanceof Pajaro){
               Pajaro p = (Pajaro)a;
               p.volar();
           }
       }
    }
}

La salida de nuestro programa

Soy un pájaro y como alpiste
Soy Pepito, un pajaro y puedo volar
Soy un león y como carne
Soy un león y estoy corriendo

Os dejo la entrada de la Wikipedia acerca del Polimorfismo para que le peguéis un vistazo

También os dejo una entrada donde se explica como haciendo uso de la Herencia y el Polimorfismo podemos crear Arrays de cualquier tipo.

Espero que os haga gustado y recordad:

  • Suscribiros para que os lleguen las nuevas entradas (es gratis!!!!).
  • Dejad algún comentario en el blog (es gratis!!!)
  • Compartir en redes sociales (es gratis!!!)

Un saludo


COMPARTIR EN REDES SOCIALES

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *