Tenía pensado escribir sobre el enlace dinámico y estático en los Tipos de Java, pero creo que para que un programador pueda entender correctamente estos conceptos, primero ha de estar familiarizado con el concepto de polimorfismo en Java y en POO.

En el artículo voy a hablar tanto en términos generales (aplicables a cualquier lenguaje POO) como específicos. No todos los lenguajes tienen los mismos tipos de polimorfismo que Java, pero creo que leyendo todo se conseguirá obtener un nivel de comprensión de este concepto mucho mayor, que a veces es un tema que es conocido, pero que no se entiende completamente.

 

Polimorfismo en Java

El polimorfismo es la habilidad de una función, método, variable u objeto de poseer varias formas distintas. Podríamos decir que un mismo identificador comparte varios significados diferentes.

El propósito del polimorfismo es implementar un estilo de programación llamado envío de mensajes en el que los objetos interactúan entre ellos mediante estos mensajes, que no son más que llamadas a distintas funciones.

Java tiene 4 grandes formas de polimorfismo (aunque conceptualmente, muchas más):

 

Polimorfismo de asignación

El polimorfismo de asignación es el que está más relacionado con el enlace dinámico.

En java, una misma variable referenciada (Clases, interfaces…) puede hacer referencia a más de un tipo de Clase. El conjunto de las que pueden ser referenciadas está restringido por la herencia o la implementación.

Esto significa, que una variable A declarada como un tipo, puede hacer referencia a otros tipos de variables siempre y cuando haya una relación de herencia o implementación entre A y el nuevo tipo. Podemos decir que un tipo A y un tipo B son compatibles si el tipo B es una subclase o implementación del tipo A.

Supongamos este ejemplo:

abstract class vehiculo
{
    abstract public void iniciar();
}
class Coche extends Vehiculo
{
    @Override
    public void iniciar()
    {
    }
}

En él tenemos una clase que hereda de otra. La forma normal de instanciar una clase de tipo Coche sería esta…

Coche j = new Coche();

Sin embargo, el polimorfismo de asignación permite a una variable declarada como otro tipo usar otra forma, siempre y cuando haya una relación de herencia o implementación. Sabiendo esto, este fragmento demuestra el polimorfismo de asignación:

Vehiculo j = new Coche();

En el vemos como una variable inicializada como tipo Vehiculo puede usar el polimorfismo de asignación para hacer referencia a una clase de tipo Coche. Podemos decir que el tipo estático de la variable j es Vehiculo, mientras que su tipo dinámico es Coche, pero de esto hablaré en otro momento…

Esto también puede hacerse con el nombre de interfaces implementadas.

interface Comprable
{
    public void comprar();
}

class Casa implements Comprable
{
    @Override
    public void comprar()
    {
    }
}

class Coche extends Vehiculo implements comprable
{
   @Override
   public void iniciar()
   {
   }

   @Override
   public void comprar()
   {
   }
}

Teniendo las anteriores clases iniciales, el siguiente código es una clara muestra del polimorfismo de asignación en Java.

Comprable a = new Casa();

a = new Coche();

Polimorfismo Puro

El polimorfismo puro se usa para nombrar a una función o método que puede recibir varios tipos de argumentos en tiempo de ejecución. Esto no lo debemos confundir con la sobrecarga, que es otro tipo de polimorfismo en tiempo de compilación. Conociendo el polimorfismo de asignación, podemos hacer una función que acepte varios tipos de objetos distintos en tiempo de ejecución. Veamos un ejemplo, usando las clases anteriormente mencionadas:

class PolimorfismoPuroTest
{
    public function funcionPolimorfica(Comprable ob)
    {
        // La función acepta cualquier "comprable", es decir, cualquier objeto que implemente esa interfaz
        // El tipo de objeto se determina en tiempo de ejecución. En nuestros ejemplos, puede ser una casa o coche.
    }
}

En el ejemplo se ve como el método funcionPolimorfica es capaz de trabajar con varios objetos gracias al polimorfismo de asignación. Esto es lo que se conoce como polimorfismo puro y cada lenguaje lo implementa de una forma u otra.

 

Polimorfismo de sobrecarga

Muy similar al anterior, pero este se realiza en tiempo de compilación.

En el polimorfismo de sobrecarga, dos o más funciones comparten el mismo identificador, pero distinta lista de argumentos. Al contrario que el polimorfismo puro, el tipado de los argumentos se especifica en tiempo de compilación.

Es muy habitual ver esto en las clases envolventes (Integer, Float, etc…) y por eso mismo voy a mostrar un ejemplo de sobrecarga de la clase String de Java:

public final class String
    implements java.io.Serializable, Comparable, CharSequence 
{
    ...

    public static String valueOf(Object obj) 
    {
        return (obj == null) ? "null" : obj.toString();
    }

    public static String valueOf(char data[]) 
    {
        return new String(data);
    }

    public static String valueOf(char data[], int offset, int count) 
    {
        return new String(data, offset, count);
    }

    ...
}

En el ejemplo anterior vemos una misma función (valueOf) con diferentes listas de argumentos. En función de los argumentos especificados en el mensaje, la clase String utilizará uno u otro para adecuarse al contexto. Fijaros que la primera función admite un Objeto, la segunda un array de Chars y la tercera Un array de Chars y dos integers. Esto es el polimorfismo de sobrecarga, de definición similar al polimorfismo puro, pero de implementación muy distinta.

Polimorfismo de inclusión

La habilidad para redefinir por completo el método de una superclase en una subclase es lo que se conoce como polimorfismo de inclusión (o redefinición).

En él, una subclase define un método que existe en una superclase con una lista de argumentos (si se define otra lista de argumentos, estaríamos haciendo sobrecarga y no redefinición).

Un ejemplo muy básico:

abstract class Pieza
{
    public abstract void movimiento(byte X, byte Y);
}

class Alfil extends Pieza
{
    @Override
    public void movimiento(byte X, byte Y)
    {
    }
}

En el ejemplo vemos como la clase Alfil sobreescribe el método movimiento. Esto es el polimorfismo de inclusión. Un error común de un desarrollador es pensar que el siguente ejemplo es otra muestra de este tipo de polimorfismo:

class Caballo extends Pieza
{
    public void movimiento(int X, int Y)
    {
    }
}

El ejemplo de la clase Caballo está sobrecargando un método definido en su superclase. No está sobreescribiendolo, porque para usar el polimorfismo de inclusión debemos usar el mismo identificador y la misma lista de parámetros que en la superclase. Fijáos que el método de la superclase usa dos bytes, mientras que el de la subclase usa dos ints.

Con esto termino esta pequeña introducción al polimorfismo en Java. Tras esto os hablaré del enlace dinámico y estático. Os añado unos cuantos enlaces donde encontraréis más información:

  • Polimorfismo [en / es]
  • Envío de mensajes [en]
  • Polymorphism in Java [Oracle]