1. Polimorfismo en colecciones: ¿para qué sirve?
Empecemos con una pregunta: «¿Para qué sirve el polimorfismo en programas reales?»
Imagina un zoológico. Tienes una clase base Animal y un montón de subclases: Dog, Cat, Cow, Parrot e incluso Platypus (ornitorrinco, para los amantes de lo exótico). Cada uno sabe emitir un sonido (makeSound()), pero lo hace a su manera.
En lugar de crear arrays separados para cada animal, declaras un array o lista del tipo Animal y metes ahí a quien quieras:
Animal[] animals = {
new Dog(),
new Cat(),
new Cow(),
new Parrot()
};
Ahora puedes recorrer este array y llamar a makeSound() para cada uno:
for (Animal animal : animals) {
animal.makeSound();
}
¡Magia! Cada objeto sabe por sí mismo qué sonido emitir y no necesitas escribir montones de if o switch.
Ejemplo con analogía
Es como si dieras la orden «¡Voz!» a un grupo de animales, y cada uno decide por sí mismo qué hacer: el perro ladra, el gato maúlla y la vaca — muge. No especificas quién es quién; simplemente llamas al mismo método.
2. Ejemplo práctico: jerarquía de empleados
Hagamos un ejemplo más cercano a la vida real (y a tu futuro trabajo en IT). Imaginemos que tenemos una empresa con distintos empleados: gerentes, desarrolladores y testers. Cada uno tiene un método work(), pero su implementación es diferente.
Declaración de la clase base
public class Employee {
public void work() {
System.out.println("El empleado trabaja...");
}
}
Subclases
public class Manager extends Employee {
@Override
public void work() {
System.out.println("El gerente dirige una reunión.");
}
}
public class Developer extends Employee {
@Override
public void work() {
System.out.println("El desarrollador escribe código.");
}
}
public class Tester extends Employee {
@Override
public void work() {
System.out.println("El tester busca errores.");
}
}
Uso de un array/lista del tipo base
public class CompanyDemo {
public static void main(String[] args) {
Employee[] team = {
new Manager(),
new Developer(),
new Tester(),
new Developer()
};
for (Employee e : team) {
e.work(); // Se invocará la versión "correcta" del método para cada objeto
}
}
}
Resultado de la ejecución:
El gerente dirige una reunión.
El desarrollador escribe código.
El tester busca errores.
El desarrollador escribe código.
¿Cuáles son las ventajas?
- No escribes un montón de comprobaciones del tipo «Si es Developer — haz esto».
- Para añadir un nuevo empleado (por ejemplo, Designer) basta con crear una nueva clase y añadirla al array.
- ¡El código que utiliza el array de empleados no cambia en absoluto!
3. Ventajas del polimorfismo: flexibilidad y extensibilidad
Imaginemos que en tu empresa aparece un nuevo tipo de empleado — Designer. Todo lo que necesitas hacer es crear una nueva clase:
public class Designer extends Employee {
@Override
public void work() {
System.out.println("El diseñador diseña maquetas.");
}
}
Ahora puedes añadir al diseñador al equipo:
Employee[] team = {
new Manager(),
new Developer(),
new Tester(),
new Designer()
};
¡Voilà! El programa empieza a trabajar correctamente con el nuevo tipo de empleado sin cambiar ni una sola línea en el código que recorre el array y llama a work().
Esto es la extensibilidad: tu código se adapta fácilmente a nuevos tipos de objetos.
4. Limitaciones del polimorfismo: el reverso de la moneda
Por desgracia, toda «magia» tiene sus límites (y su precio, como en cualquier RPG).
Solo están disponibles los métodos de la clase base
Cuando trabajas con una variable del tipo Employee, solo puedes llamar a los métodos que están declarados en Employee. Si en Developer hay un método específico writeCode(), no podrás llamarlo directamente:
Employee e = new Developer();
// e.writeCode(); // Error de compilación: ese método no existe en Employee.
Si realmente quieres llamar a un método específico, tendrás que hacer una conversión de tipos. Pero esto es el último recurso. Si conviertes tipos a menudo, quizá debas revisar el diseño de las clases: la clase base o la interfaz deberían contener el método necesario.
if (e instanceof Developer) {
Developer dev = (Developer) e;
dev.writeCode();
}
Pero entonces pierdes la universalidad y la elegancia por las que se planteó todo esto. Por ello, intenta diseñar la clase base de forma que incluya solo los métodos que realmente necesitan todos los descendientes.
5. Práctica: implementamos una jerarquía de empleados
Combinemos lo agradable con lo útil: escribamos una aplicación sencilla con varios tipos de empleados y usemos polimorfismo para tratarlos.
Paso 1: clase base y subclases
// Employee.java
public class Employee {
public void work() {
System.out.println("El empleado trabaja...");
}
}
// Manager.java
public class Manager extends Employee {
@Override
public void work() {
System.out.println("El gerente dirige una reunión.");
}
}
// Developer.java
public class Developer extends Employee {
@Override
public void work() {
System.out.println("El desarrollador escribe código.");
}
}
// Tester.java
public class Tester extends Employee {
@Override
public void work() {
System.out.println("El tester busca errores.");
}
}
Paso 2: clase principal
// CompanyDemo.java
public class CompanyDemo {
public static void main(String[] args) {
Employee[] team = {
new Manager(),
new Developer(),
new Tester(),
new Developer()
};
for (Employee e : team) {
e.work();
}
}
}
Paso 3: añadimos extensibilidad
Supongamos que dentro de un mes aparece un nuevo empleado — un diseñador. Todo lo que hay que hacer:
public class Designer extends Employee {
@Override
public void work() {
System.out.println("El diseñador diseña maquetas.");
}
}
Listo, ahora se puede añadir al diseñador al equipo:
Employee[] team = {
new Manager(),
new Developer(),
new Tester(),
new Designer()
};
Conclusión
¡Todo el código principal (CompanyDemo) se mantuvo sin cambios! Esa es la fuerza del polimorfismo.
6. Errores típicos al utilizar el polimorfismo
Error n.º 1: Esperar acceso a métodos específicos a través de una referencia del tipo base.
Muy a menudo los principiantes intentan llamar a métodos específicos de la subclase a través de una variable del tipo de la superclase. Por ejemplo:
Employee e = new Developer();
// e.writeCode(); // ¡Error! Ese método no está definido en Employee.
Para llamar a un método específico hay que convertir el tipo, pero entonces se pierde la universalidad.
Error n.º 2: No usar la anotación @Override.
Si olvidas la anotación, puedes escribir accidentalmente un método nuevo en lugar de sobrescribirlo (por ejemplo, equivocándote en el nombre). Entonces el polimorfismo simplemente no funcionará y se llamará a la versión del supertipo.
Error n.º 3: Ausencia de una interfaz común.
Si la clase base no contiene el método necesario, el polimorfismo es imposible. Por ejemplo, si en Employee no hay un método work(), entonces el bucle sobre el array de empleados no podrá llamar a ese método para todos.
Error n.º 4: Violación del principio abierto/cerrado.
Si para añadir un nuevo tipo de empleado hay que cambiar el código que recorre el array/lista, significa que no estás utilizando correctamente el polimorfismo.
GO TO FULL VERSION