Una clase abstracta es una clase que no se puede instanciar directamente y puede dejar operaciones sin implementar.
En el artículo anterior creamos una jerarquía donde Coche heredaba de Vehiculo. Todo funcionaba bien: podíamos crear un new Coche() y un new Vehiculo().
Pero párate a pensar un segundo. ¿Tiene sentido hacer new Vehiculo()?
Si vas a un concesionario y dices “quiero comprar un vehículo”, el vendedor te preguntará “¿Cuál? ¿Un coche, una moto, un camión?”. No puedes comprar un “vehículo” a secas. El vehículo es un concepto abstracto, una categoría, no un objeto físico concreto.
En programación pasa lo mismo. A veces tenemos clases padre que sirven para agrupar lógica común, pero que no deberían poder instanciarse nunca porque están incompletas.
Para eso existen las Clases Abstractas.
Diferencias: abstracta vs concreta
| Característica | Clase Concreta | Clase Abstracta |
|---|---|---|
| Instanciación | Se puede hacer new. | Prohibido hacer new. |
| Métodos | Todos deben tener cuerpo (código). | Puede mezclar métodos normales y abstractos. |
| Uso | Definir objetos reales. | Definir plantillas base y contratos parciales. |
| Herencia | Puede ser final (hoja del árbol). | Está diseñada para ser heredada. |
¿Qué es una clase abstracta?
Una clase abstracta es una clase que no se puede instanciar. Sirve únicamente como base para que otras clases hereden de ella.
Se define con la palabra clave abstract.
// Definición de la clase abstracta
public abstract class Animal {
protected String nombre;
public Animal(String nombre) {
this.nombre = nombre;
}
// Método CONCRETO (Tiene código y funciona)
// Todos los animales duermen igual, así que lo implementamos aquí.
public void dormir() {
System.out.println("Zzzzz... " + nombre + " está durmiendo.");
}
}
Si intentas hacer esto en tu main, el compilador te detendrá:
// Animal a = new Animal("Bicho"); // ¡ERROR DE COMPILACIÓN!
// 'Animal' is abstract; cannot be instantiated
¿Cuándo usar clases abstractas?
- Tienes una relación “Es-Un” clara (Un Perro es un Animal).
- Quieres compartir código: Tienes lógica común (como
dormir()omoverse()) que no quieres repetir en cada hijo. - Hay comportamientos que no puedes definir genéricamente: Sabes qué deben hacer los hijos, pero no cómo.
- Quieres prohibir instancias genéricas: Evitar que alguien cree objetos que no deberían existir por sí solos.
Si solo tienes métodos abstractos (ningún código compartido ni atributos), entonces probablemente no necesitas una Clase Abstracta, sino una Interfaz. Eso es justo lo que veremos más adelante.
Métodos abstractos: “firmar un contrato”
La verdadera potencia de las clases abstractas no es solo prohibir el new. Es la capacidad de definir Métodos Abstractos.
Un método abstracto es un método que no tiene cuerpo (no tiene llaves {}). Solo tiene la definición (el nombre y los parámetros).
public abstract class Animal {
// ... atributos y constructor ...
// MÉTODO ABSTRACTO (Sin cuerpo, termina en punto y coma)
public abstract void hacerSonido();
}
Requisito de implementación
Si una clase tiene al menos un método abstracto, la clase entera DEBE ser declarada abstract.
Implementando la abstracción
Ahora, cuando creemos las clases hijas (Perro, Gato), Java nos obligará a implementar el código de hacerSonido(). Si no lo hacemos, el código no compilará.
public class Perro extends Animal {
public Perro(String nombre) {
super(nombre);
}
// OBLIGATORIO: Sobrescribir el método abstracto
@Override
public void hacerSonido() {
System.out.println("¡Guau Guau!");
}
}
public class Gato extends Animal {
public Gato(String nombre) {
super(nombre);
}
@Override
public void hacerSonido() {
System.out.println("¡Miau!");
}
}
Polimorfismo con clases abstractas
Aquí es donde todo encaja. Aunque no podemos hacer new Animal(), sí podemos usar Animal como tipo de referencia para guardar a sus hijos.
// Referencia del Padre (Abstracto) = Instancia del Hijo (Concreto)
Animal miMascota = new Perro("Firulais");
miMascota.dormir(); // Usa el código compartido en Animal
miMascota.hacerSonido(); // Usa el código específico de Perro ("Guau")
Esto nos permite tener, por ejemplo, un Animal[] zoologico y recorrerlo llamando a hacerSonido() para todos, sin importar si son perros, gatos o leones. Cada uno sonará correctamente.