CodeGym /Cursos /JAVA 25 SELF /Relación entre polimorfismo y clases abstractas

Relación entre polimorfismo y clases abstractas

JAVA 25 SELF
Nivel 18 , Lección 4
Disponible

1. Clases y métodos abstractos

A veces en la vida (y en programación) apetece decir: «Bueno, no sé exactamente cómo se hace, pero sí sé que debe existir». Por ejemplo, todos los animales deben saber emitir un sonido, pero cuál exactamente depende del animal concreto. Para estos casos en Java se inventaron las clases abstractas y los métodos abstractos.

Una clase abstracta es una clase que no puede instanciarse directamente (no puedes escribir new Animal() si Animal es abstracta), pero de la que sí se puede heredar. Dicha clase puede contener tanto métodos normales (implementados) como abstractos, es decir, declarados pero no implementados.

Un método abstracto es un método sin cuerpo. Se declara con la palabra clave abstract y debe implementarse obligatoriamente en las subclases (salvo que la subclase también sea abstracta).

Un ejemplo de la vida real

Supongamos que tenemos una aplicación para un zoológico. Queremos que todos los animales tengan el método makeSound(), pero no sabemos qué sonido emite cada uno. Entonces creamos una clase abstracta:

public abstract class Animal {
    public abstract void makeSound(); // Método abstracto
}

Y los animales concretos implementan este método a su manera:

public class Dog extends Animal {
    @Override
    public void makeSound() {
        System.out.println("¡Guau-guau!");
    }
}

public class Cat extends Animal {
    @Override
    public void makeSound() {
        System.out.println("¡Miau!");
    }
}

Ahora, si alguien intenta crear new Animal(), el compilador dirá enseguida: «Lo siento, pero los animales abstractos no existen en la naturaleza». Esto es útil: garantizas que en el programa solo existirán animales concretos con comportamientos concretos.

2. Polimorfismo mediante abstracción

La abstracción es destacar la interfaz común para un grupo de objetos. La clase abstracta precisamente define esa interfaz común: indica qué métodos deben implementar obligatoriamente todas las subclases.

El polimorfismo y la abstracción funcionan en pareja: la clase abstracta garantiza que todos los descendientes tienen los métodos necesarios, y el polimorfismo permite invocar esos métodos a través de una referencia al tipo base.

Ejemplo: creamos un zoológico

Construyamos un pequeño zoológico. Tenemos la clase abstracta Animal y varios de sus descendientes:

public abstract class Animal {
    public abstract void makeSound();
}
public class Cow extends Animal {
    @Override
    public void makeSound() {
        System.out.println("¡Muu!");
    }
}
public class Duck extends Animal {
    @Override
    public void makeSound() {
        System.out.println("¡Cuac-cuac!");
    }
}

Ahora podemos crear un array de animales:

Animal[] zoo = {
    new Dog(),
    new Cat(),
    new Cow(),
    new Duck()
};

for (Animal animal : zoo) {
    animal.makeSound(); // En cada animal se invocará el método "correcto"
}

Cada objeto del array es un animal concreto, pero para el código es simplemente un Animal. Gracias al polimorfismo y a la abstracción podemos estar seguros de que cada objeto tiene el método makeSound() y de que funcionará correctamente.

3. Uso de clases abstractas para el polimorfismo

Veamos un ejemplo más práctico. Imaginemos que desarrollamos una aplicación para gestionar empleados de una empresa. Tenemos distintos tipos de empleados: managers, desarrolladores, testers. Todos tienen un método común work(), pero lo realizan de forma distinta.

Clase abstracta Employee

public abstract class Employee {
    protected String name;

    public Employee(String name) {
        this.name = name;
    }

    public abstract void work();
}

Subclases concretas

public class Manager extends Employee {
    public Manager(String name) {
        super(name);
    }

    @Override
    public void work() {
        System.out.println(name + " dirige al equipo.");
    }
}

public class Developer extends Employee {
    public Developer(String name) {
        super(name);
    }

    @Override
    public void work() {
        System.out.println(name + " escribe código.");
    }
}

public class Tester extends Employee {
    public Tester(String name) {
        super(name);
    }

    @Override
    public void work() {
        System.out.println(name + " prueba la aplicación.");
    }
}

Uso del polimorfismo

Ahora podemos crear un array de empleados y llamar para cada uno al método work():

Employee[] employees = {
    new Manager("Anna"),
    new Developer("Ivan"),
    new Tester("Mariya")
};

for (Employee e : employees) {
    e.work();
}

Resultado:

Anna dirige al equipo.
Ivan escribe código.
Mariya prueba la aplicación.

Observa que en el bucle no sabemos (¡ni queremos saber!) cuál es el tipo concreto de empleado. Simplemente invocamos work(), y cada objeto hace lo suyo.

4. Aspectos útiles

Garantía de implementación de métodos
La clase abstracta obliga a todas las subclases a implementar los métodos necesarios. Si olvidas implementar un método abstracto en una subclase, el compilador te lo reprochará al instante: «¡Debes hacerlo!»

Interfaz universal
El código que trabaja con un array o una lista de un tipo abstracto (Employee[], List<Animal>) puede ser totalmente universal. Puedes añadir nuevas subclases y no tendrás que cambiar el código principal.

Protección contra objetos «indebidos»
Como no se puede instanciar una clase abstracta directamente, nadie podrá crear por accidente un objeto de tipo «extraño» que no implemente los métodos necesarios.

Teoría y sintaxis: cómo declarar una clase y un método abstractos

  • Una clase abstracta se declara con la palabra clave abstract antes de class.
  • Un método abstracto se declara con la palabra clave abstract y no tiene cuerpo (solo punto y coma).
  • Una clase con al menos un método abstracto debe ser abstracta.
  • La clase que hereda de una clase abstracta debe implementar todos sus métodos abstractos o, de lo contrario, también debe ser abstracta.

Esquema

public abstract class Animal {
    public abstract void makeSound();
}

public class Cat extends Animal {
    @Override
    public void makeSound() {
        System.out.println("¡Miau!");
    }
}

5. Errores comunes al trabajar con clases abstractas

Error n.º 1: intentar crear un objeto de una clase abstracta.
Código como new Animal() no compilará. Las clases abstractas son como un manual de montaje sin las piezas: hasta que no exista una subclase concreta, no se puede montar el objeto.

Error n.º 2: olvidar implementar el método abstracto en la subclase.
Si declaras un método abstracto pero no lo implementas en el descendiente (y no haces la clase también abstracta), el compilador se enfadará y mostrará un error.

Error n.º 3: olvidar los modificadores de acceso.
Un método sobrescrito no puede tener un modificador de acceso más restrictivo que en la clase base. Por ejemplo, si el método abstracto era public, entonces la implementación también debe ser public (y no protected ni private).

Error n.º 4: intentar declarar un método abstracto con cuerpo.
Un método abstract no puede tener cuerpo; de lo contrario, el compilador pondrá los ojos en blanco y dirá: «¡Decídete ya: o abstracción o implementación!»

Error n.º 5: el polimorfismo no funciona con métodos estáticos.
El polimorfismo solo funciona con métodos no estáticos. Los métodos estáticos no se sobrescriben — se ocultan —, por lo que el comportamiento al invocarlos depende del tipo de la variable y no del objeto real.

1
Cuestionario/control
Polimorfismo y sobrecarga, nivel 18, lección 4
No disponible
Polimorfismo y sobrecarga
Polimorfismo y sobrecarga
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION