Polimorfismo

Según el siguiente diagrama de clases. La clase Gerente hereda de la clase Empleado y la clase Director hereda de la clase Gerente. También podemos decir que la clase  Director hereda indirectamente de Empleado.

 

RELACION IS-A

Cuando utilizamos herencia se establece una relación is-a (traducido es-un). Según el diagrama anterior se establecen las siguientes relaciones:

    • Gerente es un Empleado
    • Director es un Empleado
    • Director es un Gerente

Además de forma implícita, sabemos que Empleado hereda de Object por lo cual también se establecen las siguientes relaciones:

    • Empleado es un Object
    • Gerente es un Object
    • Director es un Object

 

REPRESENTACION DE LOS OBJETOS CREADOS

Habéis oído hablar de las muñecas matrioskas? Son esas muñecas rusas que va una  dentro de otra. Pues bien, vamos a pensar que cada objeto creado anteriormente es una muñeca. Por lo cual lo podríamos representar así.

 

Es como decir que dentro del objeto Director hay un objeto Gerente y dentro de este un objeto Empleado y dentro de este a su vez un objeto de tipo Object.

 

Esta representación nos va a ayudar a entender otra de las características de la Programación Orientada a Objetos. Nos referimos al polimorfismo.

 

Un objeto sólo tiene una forma (aquella que se le asigna cuando se construye). Sin embargo, una variable es polimórfica porque puede hacer referencia a objetos de distintas formas. Lo que queremos decir, es que el objeto no cambia. Lo que cambia es la forma como lo vemos.

 

El lenguaje Java, como la mayoría de los lenguajes de programación OO, en realidad permite hacer referencia a un objeto con una variable que es uno de los tipos de una superclase. Por tanto, es posible decir que es válido poner:

 

Empleado e = new Gerente()

 

Utilizando la variable e tal cual, puede acceder únicamente a las partes del objeto que son componentes de Empleado; las partes específicas de Gerente están ocultas. Esto es porque, por lo que respecta al compilador, e es un Empleado, no un Gerente. Por lo tanto, no se permite lo siguiente:

 

LLAMADA A METODOS VIRTUALES

Supongamos que es cierto lo siguiente:

Empleado e = new Empleado();

Gerente m = new Gerente();

Si hace una llamada a e.getDetails() y a m.getDetails(), está llamando a diferentes  comportamientos. El objeto Empleado ejecuta la versión de getDetails() asociada a la clase Empleado y el objeto Gerente ejecuta la versión de getDetails() asociada a la clase Gerente.

 

Lo que no resulta tan obvio es lo que ocurre si tenemos el código siguiente:

Empleado e = new Gerente();

e.getDetails();

O algo parecido, como un argumento de un método general o un elemento de una colección heterogénea.

De hecho, se obtiene el comportamiento asociado al objeto al que hace referencia la variable durante el tiempo de ejecución. El comportamiento no está determinado por el tipo de la variable en el momento de la compilación. Este es un aspecto del polimorfismo y un aspecto importante de los lenguajes orientados a objetos. Este comportamiento a menudo recibe el nombre de llamada a métodos virtuales.

En el ejemplo anterior, el método e.getDetails() ejecutado procede del tipo real del objeto, un Gerente.

 

COLECCIONES HETEROGÉNEAS

Es posible crear colecciones de objetos que tienen una clase común. Este tipo de colecciones se denominan homogéneas. Por ejemplo:

 

El lenguaje Java incluye una clase Object que permite crear colecciones de todos los tipos de elementos porque todas las clases amplían la clase Object. Estas colecciones se denominan heterogéneas.

Una colección heterogénea es aquella que se compone de elementos dispares. En los lenguajes OO, es posible crear colecciones de numerosos elementos. Todas ellas tienen una clase de origen común: la clase Object. Por ejemplo:

Es posible escribir incluso un método de ordenación que coloque los empleados por orden de edad o salario, con independencia de que algunos de esos empleados sean gerentes.

 

ARGUMENTOS POLIMÓRFICOS

Es posible escribir métodos que acepten un objeto genérico (en este caso, la clase  Empleado) y funcionen adecuadamente con los objetos de cualquier subclase de dicho objeto. Por ejemplo, podría generarse un método en una clase de aplicación que tome un empleado y lo compare con un determinado umbral de salario para determinar las obligaciones fiscales de ese empleado. Si se utilizan las características polimórficas, es posible hacerlo como sigue:

 

Esto es válido porque un Gerente es un Empleado. No obstante, el método  hallarTipoImpositivo sólo tiene acceso a los miembros (variables y métodos) que estén definidos en la clase Empleado.

 

OPERADOR INSTANCEOF

Dado que es posible pasar objetos de un lugar a otro utilizando referencias a sus superclases, a veces conviene saber de qué objetos reales se dispone. Éste es el  propósito del operador instanceof.

Si recibe un objeto utilizando una referencia del tipo Empleado, puede que resulte ser un Gerente o un Ingeniero. Puede comprobarlo utilizando instanceof de la forma siguiente:

 

CONVERSIÓN DE OBJETOS

En casos en los que se ha recibido una referencia a una clase de nivel superior y se ha comprobado mediante el operador instanceof que el objeto es una subclase concreta, es posible acceder a toda la funcionalidad del objeto convirtiendo la referencia.

Si no realiza la conversión, el intento de ejecutar e.getDepartamento() fracasará porque el compilador no puede localizar ningún método llamado getDepartamento en la clase Empleado.

Si no realiza la comprobación con instanceof, corre el riesgo de que falle la conversión. En general, cualquier intento de convertir una referencia a un objeto se somete a diferentes comprobaciones:

 

    • Las conversiones en dirección ascendente dentro de la jerarquía de clases siempre están permitidas y, de hecho, no precisan el operador de conversión. Se pueden hacer mediante una simple asignación.
    •  En el caso de las conversiones en dirección descendente, el compilador debe poder considerar que la conversión es, al menos, posible. Por ejemplo, no se permitirá ningún intento de convertir una referencia a Gerente en una referencia a Ingeniero, porque el Ingeniero no es un Gerente. La clase de destino de la conversión tiene que ser una subclase del tipo de referencia actual.
    • Si el compilador permite la conversión, el tipo de objeto se comprueba durante el tiempo de ejecución. Por ejemplo, si se omite la comprobación instanceof en el código y el objeto que se va a convertir en realidad no es del tipo en el que se va a convertir, se producirá un error de tiempo de ejecución (excepción).

 

Código de ejemplo con polimorfismo