Polimorfismo

POLIMORFISMO EN JAVA, ENLAZADO DINÁMICO

El polimorfismo es una de las características fundamentales de la Programación Orientada a Objetos y está estrechamente relacionado con la herencia.
Una jerarquía de clases, los métodos y clases abstractas, 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. En esta entrada se explican y se ven algunos ejemplos de herencia.
El polimorfismo se puede definir como la cualidad que tienen los objetos para responder de distinto modo a un mismo mensaje.

Para conseguir un comportamiento polimórfico en un programa Java se debe cumplir lo siguiente:
- 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 debes estar redefinidos en las clases derivadas.
- Los objetos deben ser manipulados utilizando referencias a la clase base.
Ejemplo de polimorfismo en Java:
Vamos a ver más claro todo lo anterior con un ejemplo. Tenemos la siguiente clase abstracta Polígono:
//Clase abstracta Poligono
public abstract class Poligono {
    private int numLados;

    public Poligono() {
    }

    public Poligono(int numLados) {
        this.numLados = numLados;
    }

    public int getNumLados() {
        return numLados;
    }


    public void setNumLados(int numLados) {
        this.numLados = numLados;
    }
   
    //Sobreescritura del método toString() heredado de Object
    @Override
    public String toString(){
        return "Numero de lados: " + numLados;
    }
   
    //Declaración del método abstracto area()
    public abstract double area();
   
}
Esta clase tiene un atributo entero numLados. Además contiene el método abstracto area() y se ha modificado (Override) el método toString() heredado de Object.
A partir de la clase Poligono vamos a crear las clases Rectangulo y Triangulo como derivadas de ella.
//Clase Rectangulo hereda de Poligono
public class Rectangulo extends Poligono{
    private double lado1;
    private double lado2;

    public Rectangulo() {
    }

    public Rectangulo(double lado1, double lado2) {
        super(2);
        this.lado1 = lado1;
        this.lado2 = lado2;
    }

    public double getLado1() {
        return lado1;
    }

    public void setLado1(double lado1) {
        this.lado1 = lado1;
    }

    public double getLado2() {
        return lado2;
    }

    public void setLado2(double lado2) {
        this.lado2 = lado2;
    }

    // Sobreescritura del método toString() heredado de Poligono
    @Override
    public String toString() {
        return "Rectangulo " + super.toString() +
                  "\nlado 1 = " + lado1 + ", lado 2 = " + lado2;
    }

    //Implementación del método abstracto area() heredado de Poligono
    @Override
    public double area(){
        return lado1 * lado2;
    }
}


//Clase Triangulo hereda de Poligono
public class Triangulo extends Poligono{
    private double lado1;
    private double lado2;
    private double lado3;
    public Triangulo() {
    }
    public Triangulo(double lado1, double lado2, double lado3) {
        super(3);
        this.lado1 = lado1;
        this.lado2 = lado2;
        this.lado3 = lado3;
    }

    public double getLado1() {
        return lado1;
    }

    public void setLado1(double lado1) {
        this.lado1 = lado1;
    }

    public double getLado2() {
        return lado2;
    }

    public void setLado2(double lado2) {
        this.lado2 = lado2;
    }

    public double getLado3() {
        return lado3;
    }

    public void setLado3(double lado3) {
        this.lado3 = lado3;
    }

    // Sobreescritura del método toString() heredado de Poligono
    @Override
    public String toString() {
        return "Triangulo " + super.toString() +
                  "\nlado 1 = " + lado1 + ", lado 2 = " + lado2 + ", lado 3 = " + lado3;
    }
   
    //Implementación del método abstracto area() heredado de Poligono
    @Override
    public double area(){
        double p = (lado1+lado2+lado3)/2;
        return Math.sqrt(p * (p-lado1) * (p-lado2) * (p-lado3));
    }
}
Vamos a escribir un programa para utilizar estas clases y ver como funciona el polimorfismo. Vamos a crear objetos de tipo Triángulo y Rectángulo y los guardaremos en un ArrayList. En lugar de tener dos arrays distintos uno para triángulos y otro para rectángulos, los guardaremos todos juntos utilizando un ArrayList de Poligonos.
El programa creará objetos, leerá sus datos por teclado y los guardará en el ArrayList de Polígonos. A continuación se recorrerá el array y se mostrarán los datos de cada objeto y su área.
//Clase Principal
public class Polimorfismo1 {

    static Scanner sc = new Scanner(System.in);
    // ArrayList de referencias a objetos de la clase base Poligono
    static ArrayList<Poligono> poligonos = new ArrayList<Poligono>();
   
    public static void main(String[] args) {
        leerPoligonos();
        mostrarPoligonos();
    }

    //Se pide por teclado el tipo de Poligono a leer y se ejecuta el método
    //leer correspondiente
    public static void leerPoligonos() {
        int tipo;
        do {
            do {
                System.out.print("Tipo de poligono 1-> Rectangulo 2-> Triangulo 0-> FIN >>> ");
                tipo = sc.nextInt();
            } while (tipo < 0 || tipo > 2);
            if (tipo != 0) {
                switch (tipo) {
                    case 1:
                        leerRectangulo();
                        break;
                    case 2:
                        leerTriangulo();
                        break;
                }
            }
        } while (tipo != 0);
    }

    //Se crea un rectángulo y se añade al array
    public static void leerRectangulo() {
        double l1, l2;
        System.out.println("Introduzca datos del Rectángulo");
        do {
            System.out.print("Longitud del lado 1: ");
            l1 = sc.nextDouble();
        } while (l1 <= 0);
        do {
            System.out.print("Longitud del lado 2: ");
            l2 = sc.nextDouble();
        } while (l2 <= 0);
        Rectangulo r = new Rectangulo(l1, l2);
        poligonos.add(r);
        //En esta instrucción se produce una conversión implícita o upcasting
        //Se asigna una referencia de una clase derivada (Rectangulo)
        //a una referencia de la clase base (Poligono) ya que el ArrayList es
        //de tipo Poligono
    }

    //Se crea un triángulo y se añade al array
    Public static void leerTriangulo() {
        double l1, l2, l3;
        System.out.println("Introduzca datos del Triangulo");
        do {
            System.out.print("Longitud del lado 1: ");
            l1 = sc.nextDouble();
        } while (l1 <= 0);
        do {
            System.out.print("Longitud del lado 2: ");
            l2 = sc.nextDouble();
        } while (l2 <= 0);
        do {
            System.out.print("Longitud del lado 3: ");
            l3 = sc.nextDouble();
        } while (l3 <= 0);
        Triangulo t = new Triangulo(l1, l2, l3);
        poligonos.add(t);
        //conversión implícita o upcasting igual que en el método anterior
    }

    public static void mostrarPoligonos() {
        //Se recorre el ArrayList poligonos que contiene
        //referencias a Triangulos y Rectangulos.
        //A p de tipo Poligono se le asignarán mediante upcasting referencias a objetos
        //de tipo Triangulo o Rectangulo
        for(Poligono p: poligonos){
           
            System.out.print(p.toString());
            System.out.printf(" area: %.2f %n", p.area());
        }
    }
}
En estas dos instrucciones se produce el polimorfismo:
System.out.print(p.toString());
System.out.printf(" area: %.2f %n", p.area());
Después de ejecutar el método leerPoligonos, el ArrayList poligonos contiene mezclados triángulos y rectángulos. Por ejemplo, podría contener lo siguiente:
triangulo
triangulo
rectangulo
triangulo
rectangulo
rectangulo
triangulo
Mediante el bucle for
for(Poligono p: poligonos)
se recorre el array y se asigna a la variabale p de tipo Poligono (Clase base) cada elemento del array. En la instrucción p.toString() mediante p se invoca al método toString(). Pero, ¿a qué método toString se ejecutará? Se ejecutará el método toString de las clase derivada a la que pertenece el objeto referenciado por la variable p (Triangulo o Rectangulo)
Lo mismo ocurre cuando se invoca al método area(). Se ejecutará el método area de la clase derivada a la que pertenece el objeto referenciado por p.
En este ejemplo se produce el polimorfismo ya que:
-Los métodos toString() y area() están declarados en la clase base. El método toString está además implementado en la clase base.
-Estos métodos están redefinidos en las clases derivadas.
-Se invocan mediante referencias a la clase base Poligono.

El polimorfismo es posible porque, como ya hemos visto en la entrada correspondiente a la Herencia, cuando se invoca un método mediante una referencia a una clase base, se ejecuta la versión del método correspondiente a la clase del objeto referenciado y no al de la clase de la variable que lo referencia.
Por lo tanto, el método que se ejecuta se decide durante la ejecución del programa.
A este proceso de decidir en tiempo de ejecución qué método se ejecuta se le denomina enlazado dinámico.
El enlazado dinámico es el mecanismo que hace posible el polimorfismo.
El enlazado dinámico es lo opuesto a la habitual vinculación estática o enlazado estático que consiste en decidir en tiempo de compilación qué método se ejecuta en cada caso.
En el programa anterior podemos ver donde se produce enlazado dinámico y donde enlazado estático:

public void mostrarPoligonos() {
        for(Poligono p: poligonos){
            //Para que el polimorfismo se lleve a cabo se está
            //aplicando el enlazado dinámico.
            //Durante la ejecución se decide qué método se ejecuta.
            System.out.print(p.toString());
            System.out.printf(" area: %.2f %n", p.area());
        }
}

public static void main(String[] args) {
        // Enlazado estático. Cuando se compila el programa
        //ya se sabe que serán esos métodos los que se van a ejecutar.
        leerPoligonos();
        mostrarPoligonos();
}