1. Sintaxis de una clase abstracta
Vamos a ver qué es una clase abstracta en Java y para qué sirve. Imagina que tienes un constructor de LEGO: tienes un conjunto de piezas (por ejemplo, «ruedas», «bloques»), y hay piezas especiales para modelos concretos. Una clase abstracta es como una instrucción general para manipular piezas sin describir el montaje de un modelo concreto: los bloques se encajan por pestañas, las ruedas se fijan a los ejes, las piezas se pueden apilar por capas. La clase abstracta describe estas reglas comunes y pasos obligatorios, pero no detalla el montaje de un modelo específico. Los modelos concretos son las subclases: toman la instrucción general y añaden los pasos que faltan.
En Java, una clase abstracta es una clase que no puede crearse directamente (no se puede hacer new AbstractClass()), pero de la que sí se puede heredar e implementar sus métodos «incompletos».
Las clases abstractas son útiles cuando:
- Un grupo de objetos comparte comportamiento o estado, pero parte de la lógica difiere.
- Quieres implementar parte de la funcionalidad «por defecto» y dejar otra parte para que la implementen los descendientes.
- Quieres prohibir la creación directa de objetos de esa clase (por ejemplo, «Animal» no aparece por sí solo, pero «Gato» sí).
Cómo declarar una clase abstracta
Es sencillo: antes de la palabra class escribimos la palabra clave abstract.
public abstract class Animal {
// Campos (por ejemplo, el nombre del animal)
protected String name;
// Constructor
public Animal(String name) {
this.name = name;
}
// Método abstracto: solo declaración, ¡sin implementación!
public abstract void makeSound();
// Método normal (implementado)
public void sleep() {
System.out.println(name + " duerme: Zzz...");
}
}
Particularidades:
- Una clase abstracta puede contener tanto métodos implementados como abstractos.
- Una clase abstracta puede contener campos, constructores e incluso métodos static.
- No se puede crear un objeto de una clase abstracta directamente:
-
Animal a = new Animal("Alguien"); // ¡Error!
Cómo declarar un método abstracto
Un método abstracto es un método sin cuerpo, es decir, sin llaves ni código dentro. Se declara con la palabra clave abstract y siempre termina en punto y coma ;.
public abstract void makeSound();
- Los métodos abstractos solo pueden declararse dentro de una clase abstracta.
- La clase que hereda de una clase abstracta está obligada a implementar todos los métodos abstractos; de lo contrario, también debe ser abstract.
2. Herencia de clases abstractas: cómo funciona
Veamos un ejemplo concreto. Supongamos que tenemos una clase abstracta Animal con un método abstracto makeSound(). Ahora creamos la clase hija Dog, que implementa este método:
public class Dog extends Animal {
public Dog(String name) {
super(name); // Llamada al constructor de la clase base
}
@Override
public void makeSound() {
System.out.println(name + " ladra: ¡Guau-guau!");
}
}
Y otra clase hija más:
public class Cat extends Animal {
public Cat(String name) {
super(name);
}
@Override
public void makeSound() {
System.out.println(name + " maúlla: ¡Miau!");
}
}
Ahora podemos utilizar estas clases en un programa:
public class Main {
public static void main(String[] args) {
Animal dog = new Dog("Sharik");
Animal cat = new Cat("Murka");
dog.makeSound(); // Sharik ladra: ¡Guau-guau!
cat.makeSound(); // Murka maúlla: ¡Miau!
dog.sleep(); // Sharik duerme: Zzz...
cat.sleep(); // Murka duerme: Zzz...
}
}
Presta atención:
Podemos declarar variables de tipo Animal, pero crear objetos solo de descendientes concretos (no abstractos).
Esquema: cómo se ve
. Animal (abstract)
/ \
Dog Cat
(implementa makeSound) (implementa makeSound)
3. ¿Cuándo usar una clase abstracta y cuándo una interfaz?
Es una de las preguntas más populares en entrevistas, ¡y con razón! Veámoslo:
- Clase abstracta: cuando los objetos comparten estado (por ejemplo, campos), lógica común (métodos con implementación) y quieres proporcionar un «esqueleto» de comportamiento con posibilidad de ampliación.
- Interfaz: cuando quieres definir solo un conjunto de métodos (contrato), sin implementación ni estado. Desde Java 8 las interfaces obtuvieron métodos default/static, pero aun así la interfaz trata sobre «qué debe poder hacer un objeto», no «cómo lo hace».
Ejemplo de la vida real:
«Ave» — clase abstracta: todas las aves tienen pico, alas y pueden volar (pero de forma diferente).
«Volador» — interfaz: no solo las aves vuelan, también los aviones y los superhéroes. Vuelan de manera distinta, pero lo importante es que pueden hacerlo.
4. Ejemplos prácticos
Ejemplo 1: Clase abstracta con implementación parcial
Supón que haces un juego en el que hay distintos tipos de transporte. Todos pueden moverse, pero lo hacen de forma diferente. Sin embargo, todos tienen velocidad, nombre y una forma estándar de detenerse.
public abstract class Transport {
protected String name;
protected int speed;
public Transport(String name, int speed) {
this.name = name;
this.speed = speed;
}
// Método abstracto: la implementación va en las subclases
public abstract void move();
// Método implementado
public void stop() {
System.out.println(name + " se detuvo.");
}
}
public class Car extends Transport {
public Car(String name, int speed) {
super(name, speed);
}
@Override
public void move() {
System.out.println(name + " circula por la carretera a " + speed + " km/h.");
}
}
public class Bicycle extends Transport {
public Bicycle(String name, int speed) {
super(name, speed);
}
@Override
public void move() {
System.out.println(name + " pedalea a " + speed + " km/h.");
}
}
Uso:
Transport car = new Car("Toyota", 120);
Transport bike = new Bicycle("Stels", 25);
car.move(); // Toyota circula por la carretera a 120 km/h.
bike.move(); // Stels pedalea a 25 km/h.
car.stop(); // Toyota se detuvo.
bike.stop(); // Stels se detuvo.
Ejemplo 2: Método abstracto con parámetro
public abstract class Shape {
public abstract double area();
}
public class Circle extends Shape {
private double radius;
public Circle(double radius) {
this.radius = radius;
}
@Override
public double area() {
return Math.PI * radius * radius;
}
}
public class Rectangle extends Shape {
private double width, height;
public Rectangle(double width, double height) {
this.width = width;
this.height = height;
}
@Override
public double area() {
return width * height;
}
}
Uso:
Shape c = new Circle(3);
Shape r = new Rectangle(4, 5);
System.out.println("Área del círculo: " + c.area());
System.out.println("Área del rectángulo: " + r.area());
5. Particularidades y matices de las clases abstractas
- Una clase abstracta puede tener cualquier modificador de acceso: public, protected, private (por ejemplo, para campos).
- Se puede declarar una clase abstracta sin métodos abstractos. Esto es útil si solo quieres prohibir la creación de instancias de la clase base.
- Si una clase hereda de una clase abstracta pero no implementa todos los métodos abstractos, también debe declararse como abstract.
- Una clase abstracta puede tener constructores. Se invocan al crear un objeto descendiente (mediante super(...)).
- Los métodos abstractos no pueden ser private (de lo contrario no podrían implementarse en la subclase).
- Las clases abstractas pueden contener métodos y campos static.
- Una clase abstracta puede implementar interfaces, pero no está obligada a implementar sus métodos: pueden hacerlo los descendientes.
Tabla: comparación entre clase abstracta e interfaz
| Clase abstracta | Interfaz | |
|---|---|---|
| Palabra clave | |
|
| Puede contener campos | Sí (cualesquiera) | Desde Java 8 — solo static/final |
| Métodos con implementación | Sí | Desde Java 8 — default/static |
| Métodos abstractos | Sí | Sí |
| Herencia múltiple | No | Sí (se pueden implementar múltiples interfaces) |
| Constructores | Sí | No |
| Se puede crear un objeto | No | No |
6. Errores típicos al trabajar con clases y métodos abstractos
Error n.º 1: intentar crear una instancia de una clase abstracta.
Si escribes new Animal("Alguien"), el compilador recordará de inmediato que las clases abstractas no están pensadas para eso. Recuerda: la abstracción es un «esqueleto», no un «organismo vivo».
Error n.º 2: olvidar implementar todos los métodos abstractos en la subclase.
Si tu clase no implementa al menos un método abstracto de la clase base, debe declararse como abstract; de lo contrario, obtendrás un error de compilación.
Error n.º 3: declarar un método abstracto fuera de una clase abstracta.
En Java no se puede declarar un método abstracto en una clase normal (no abstracta): el compilador se quejará al instante.
Error n.º 4: intentar que un método abstracto sea private o static.
Los métodos abstractos no pueden ser private (porque entonces no se podrían sobrescribir) ni static (porque los métodos estáticos no se sobrescriben). Tampoco pueden ser final, porque final prohíbe la sobrescritura.
Error n.º 5: olvidar el modificador de acceso de un método abstracto.
Si no indicas explícitamente el modificador, el método tendrá visibilidad de paquete (package-private), lo cual puede no ser lo que pretendías.
GO TO FULL VERSION