Java es un lenguaje de programación orientado a objetos, especialmente útil cuando se requiere implementar un código bien estructurado que pueda reutilizarse.

Este tipo de lenguajes se basa en 4 principios que permiten describir las relaciones entre clases: herencia, encapsulamiento, abstracción y polimorfismo. Debido a que siempre existe al menos una clase en cada programa de Java, es esencial conocer cada uno de estos principios, así como su aplicación.

En este artículo centraremos nuestra atención en el cuarto principio que puedes utilizar Java: el polimorfismo. Revisaremos qué es este principio, de qué manera relaciona clases y cómo puedes emplearlo del mejor modo.

<< [Plantilla gratuita] >> Plantillas de códigos de programación para Java

¿Para qué sirve el polimorfismo en Java?

A grandes rasgos, el polimorfismo permite que nombres dos acciones del mismo modo dentro de tu código, pero que cada una de ellas acepte diferentes parámetros. Esto facilita que identifiques objetos nominalmente, pero que cada uno de ellos desempeñe una función en particular.

Pensemos, por ejemplo, que quieres llamar Multiply() a dos funciones. Una de ellas será utilizada para calcular el producto de dos números enteros, mientras que la otra tendrá como función calcular el producto de dos decimales. Tu código deberá verse así:

public class Main {

public static void main(String[ ] args) {

// usando la primera función

Multiplier.Multiply(3,5);

// usando la segunda función

Multiplier.Multiply(3.5,5.1);

}

}

Cuando el código sea ejecutado, el programa deberá arrojar:

  • Producto de los enteros: 15
  • Producto de los decimales: 17,849999999999998

Pero ¿por qué no simplemente utilizar un nombre diferente? La respuesta radica en que en programación siempre debe promoverse un sistema abierto-cerrado para tener un código limpio y claro. Esto significa que un código debe estar abierto a la extensión, pero cerrado a la modificación. Si, por ejemplo, quieres ampliar las capacidades de un código, no deberás crear una nueva clase, sino extender sus capacidades agregando nuevas funcionalidades. Es siempre preferible hacerlo mediante herencia y polimorfismo.

Diferencias entre herencia y polimorfismo

Es un error común confundir herencia con polimorfismo pues ambos sirven para funciones similares. Sin embargo, es importante recalcar que son sustancialmente diferentes. 

En la herencia, la clase que está anidada dentro de otra adquiere los métodos y atributos de la clase madre. O, lo que es igual, sus funciones. Mira este ejemplo:

class Forma {

// métodos de la clase Forma

}

class Cuadrado extends Forma {

// gracias a que la clase Cuadrado se encuentra anidada dentro de la clase Forma, esta última es accesible en cualquier parte del código

// métodos de la clase Cuadrado

}

Cuando tenemos herencia dentro de un código, la clase hija hereda los mismos atributos y métodos de la clase en la que está anidada, sin modificar su funcionalidad. Por el contrario, en el caso del polimorfismo, la clase anidada recibe los mismos atributos y métodos que la clase madre, pero cuenta con su propia implementación para cada método.

Podríamos decir que la herencia es una forma de reutilizar código, mientras que el polimorfismo es una forma de dinamizarlo y simplificar el uso de diferentes funciones. Esto se logra porque el programa reconocerá qué función debe cumplir dependiendo de los parámetros que se ingresen en la clase.

Tipos y ejemplos de polimorfismo en Java

Java está optimizado para dos tipos de polimorfismo:

  • Polimorfismo estático (o de compilación)
  • Polimorfismo dinámico (o de ejecución)

Polimorfismo estático

Este tipo de polimorfismo, también conocido como de compilación, se utiliza para crear múltiples métodos con el mismo nombre en la misma clase, que contengan diferentes números de parámetros o bien parámetros de distintos tipos.

En el ejemplo que veremos a continuación, podemos advertir que existen 3 métodos con el mismo nombre, pero que aceptan diferentes parámetros:

package com.hubspot;

class Multiplier {

static void Multiply(int a, int b)

{

System.out.println("Multiplicación de enteros, Resultado = "+ a*b);

}

// Método 1

static void Multiply(double a, double b)

{

System.out.println("Multiplicación de decimales, Resultado = "+ a*b);

}

// Método 2

static void Multiply(double a, double b, double c)

{

System.out.println("Tres parámetros, Multiplicación de decimales, Resultado = "+ a*b*c);

}

}

public class Main {

public static void main(String[] args) {

// usando el primer método

Multiplier.Multiply(3,5);

// usando el segundo método

Multiplier.Multiply(3.5,5.1);

// usando el tercer método Multiplier.Multiply(3.6,5.2, 6.3);

}

}

Al correr el código obtendremos como resultado:

  • Multiplicación de enteros, Resultado = 15
  • Multiplicación de decimales, Resultado = 17,849999999999998
  • Tres parámetros, Multiplicación de decimales, Resultado = 117.936

El primer método tomará 2 enteros; el segundo 2 decimales, y el tercero 3 valores con diferentes parámetros. ¿Notas que no hicimos ninguna modificación al ingresar la información? Los tres llamados son iguales, pero actúan de diferente manera al ingresar distintos tipos de parámetros.

Polimorfismo dinámico

El polimorfismo dinámico o de ejecución es aquel en el que la clase hija tiene una definición propia, pero que depende de la clase en la que está anidada.

Veamos un ejemplo:

package com.hupspot;

// Super clase

class Figura{

protected double length;

Figura(double length){

this.length = length;

} void area(){

}

}

// clase hija

class Cuadrado extends Forma{

//constructor

Cuadrado(double side){

super(side); // llamado a la clase del constructor

}

//Overriding area() method

void area(){

System.out.println("Área del cuadrado = " + length*length);

}

}

// clase hija

class Círculo extends Figura{

//constructor

Círculo(double radius){

super(radius); // llamado a la clase del constructor

}

//Overriding area() method

void area(){

System.out.println("Área del Círculo " + 3.14*length*length);;

}

}

public class Main {

public static void main(String[] args){

Figura shape = new Cuadrado(5.0);

// llamado al método de la clase Cuadrado shape.area();

shape = new Circle(5.0); // upcasting // llamado al método de la clase Círculo

shape.area();

}

}

El resultado de este código será:

  • Área del Cuadrado = 25,0
  • Área del Círculo = 75,0

Como puedes ver, el uso del mismo nombre para una clase nos ha permitido extender sus funciones sin modificar el llamado Figura.

Uso y mejores prácticas al emplear polimorfismo

Uno de los usos más comunes del polimorfismo ocurre cuando queremos llamar de forma automática y correcta un método específico, dependiendo de la clase en uso. Por ejemplo, si estamos utilizando un mismo elemento de almacenamiento para diferentes tipos de clases. 

En este ejemplo queremos que nuestro código utilice ejemplos y objetos que hemos llamado anteriormente mediante las clases hijas Círculos y Cuadrados. Para ellos deberemos crear un conjunto de tipos de Figuras:

Set<Figura> hs = new HashSet<Figura();

hs.add(circulo1);

hs.add(circulo2);

hs.add(circulo3);

hs.add(circulo4);

for(Figura hs_element : hs){

hs_element.area();

}

Otro uso del polimorfismo sucede cuando queremos reemplazar condicionales en el código. En el caso que te presentamos queremos implementar un programa que determine qué código deberá ejecutarse cuando sea llamado, dependiendo del primer parámetro que sea ingresado. Notarás que obtenemos el mismo resultado que en el ejemplo de polimorfismo anterior, aunque no de una forma tan directa.

package org.hubspot;

// superclase

class Figura{

enum Tipo {

CUADRADO,

CIRCULO

}

protected double length;

Tipo figura_tipo;

Figura(Tipo figura, double length){

this.length = length;

this.figura_tipo = figura;

}

double area(){

double area = 0;

switch (figura_tipo)

{

case CUADRADO:

area = length * length;

break;

case CÍRCULO:

area = 3.14*length * length;

break;

}

return area;

}

}

public class Main {

public static void main(String[] args) {

Shape Circle = new Figura(Figura.Tipo.CIRCULO, 10);

System.out.println("El área del círculo es = "+ Circulo.area());

Shape Square = new Figura(Figura.Tipo.CUADRADO, 10);

System.out.println("El área del cuadrado es = "+ Cuadrado.area());

}

}

Como puedes ver, el polimorfismo es una gran herramienta para trabajar con Java o con algún otro lenguaje de programación orientado a objetos.

Con nuestros ejemplos, hemos descubierto la utilidad de reutilizar código a través del uso del mismo nombre para las clases. La gran ventaja de esta propiedad es que puede utilizarse durante la compilación o la ejecución del código.

Plantillas de código de programación para Java
Plantillas de códigos de programación para Java

Publicado originalmente el 19 de diciembre de 2022, actualizado el 16 de mayo de 2023

Topics:

Java