W3docs

Clases abstractas en Java

Define implementaciones parciales en Java con clases y métodos abstractos que las subclases deben completar.

Una clase abstracta es una clase que no puede ser instanciada directamente. Existe para ser extendida. Puede combinar métodos concretos (con cuerpo) y métodos abstractos (sin cuerpo, que la subclase debe implementar) — esa combinación es lo que la diferencia tanto de una clase regular como de una interfaz.

Esta página explica cómo declarar una clase abstracta, cómo las subclases la completan, por qué las clases abstractas pueden tener estado, el patrón de método plantilla y cómo elegir entre una clase abstracta y una interfaz.

Usa una clase abstracta cuando las subclases concretas necesiten compartir estado e infraestructura, no solo un contrato de API. Si solo necesitas un contrato, una interfaz es la mejor opción. Las clases abstractas se basan en la herencia y el polimorfismo, por lo que conviene estar familiarizado con estos conceptos primero.

Cómo declararla

Añade abstract al encabezado de la clase. Añade abstract a cualquier método que no tenga cuerpo:

public abstract class Shape {
  protected final String name;
  protected Shape(String name) { this.name = name; }

  public abstract double area();             // no body — subclass must provide one

  public String describe() {                  // concrete — inherited as-is
    return name + " area=" + area();
  }
}

De esto se desprenden varias cosas:

  • new Shape("circle") es un error de compilación — las clases abstractas no pueden ser instanciadas.
  • Una subclase que no implementa todos los métodos abstractos heredados debe declararse también como abstract. Pueden existir subclases abstractas de clases abstractas.
  • Una clase abstracta puede tener un constructor — las subclases lo invocan con super(...) igual que con un padre regular.

Implementar los métodos abstractos

Una subclase concreta debe proporcionar un cuerpo para cada método abstracto heredado:

public class Circle extends Shape {
  private final double r;
  public Circle(double r) {
    super("circle");
    this.r = r;
  }
  @Override
  public double area() { return Math.PI * r * r; }
}

Ahora new Circle(2) funciona, y describe() (heredado de Shape) delega al area() de la subclase mediante despacho dinámico.

Las clases abstractas pueden tener estado

Esta es la principal razón para elegir una clase abstracta en lugar de una interfaz. El padre puede declarar campos, escribir un constructor que los inicialice y ofrecer métodos que operen sobre ese estado compartido:

public abstract class HttpHandler {
  private final String path;
  protected HttpHandler(String path) { this.path = path; }

  public final String path() { return path; }

  public abstract Response handle(Request r);     // subclass-specific behavior
}

Cada manejador concreto tiene un path; el padre lo almacena y lo expone; cada subclase solo escribe la lógica por petición. Las interfaces no pueden hacer esto por sí solas (no tienen campos de instancia).

Mezclar métodos abstractos y concretos — el método plantilla

Un patrón común: la clase abstracta implementa el flujo general como un método concreto y deja como abstractos los puntos de variación. Las subclases solo implementan las partes que difieren:

public abstract class Beverage {
  // Template — the algorithm, written once.
  public final void prepare() {
    boilWater();
    brew();              // varies
    pourIntoCup();
    addCondiments();     // varies
  }

  protected abstract void brew();
  protected abstract void addCondiments();

  private void boilWater()    { System.out.println("boiling water"); }
  private void pourIntoCup()  { System.out.println("pouring into cup"); }
}

public class Tea extends Beverage {
  protected void brew()           { System.out.println("steeping tea"); }
  protected void addCondiments()  { System.out.println("adding lemon"); }
}

Tea.prepare() ejecuta la plantilla del padre, que delega a brew y addCondiments de Tea mediante polimorfismo. Añadir una subclase Coffee solo requiere los dos métodos abstractos.

Este es el patrón de método plantilla y es la razón más habitual para elegir una clase abstracta.

Abstracto vs final

abstract y final son opuestos y el compilador lo hace cumplir:

  • abstract class — debe ser subclasificada.
  • final class — no puede ser subclasificada.

Lo mismo aplica a los métodos: los métodos abstract deben ser sobreescritos; los métodos final no pueden serlo. Usar ambos a la vez es un error de compilación.

Abstracto vs interfaz

Clase abstractaInterfaz
ConstructoresNo
Campos de instanciaNo (solo constantes public static final)
Cuerpos de métodosSí (cualquier número)Sí mediante default (con moderación)
HerenciaSimple — solo un padreMúltiple — varias interfaces
Úsala cuandoLas subclases comparten estado e infraestructuraLas subclases solo comparten un contrato de API

Una regla práctica: empieza con una interfaz. Pasa a (o añade) una clase abstracta solo si encuentras código compartido acumulándose entre implementaciones. Los métodos default de las interfaces en Java 8+ han ocupado parte del territorio que antes pertenecía a las clases abstractas, pero el caso de uso del "estado mutable compartido" sigue siendo territorio de las clases abstractas.

Los métodos abstractos no pueden ser private ni static

  • private haría el método invisible para las subclases — no podrían sobreescribirlo, por lo que la abstracción sería inejecutable.
  • Los métodos static no se despachan dinámicamente — no pueden ser sobreescritos, solo ocultados — por lo que un método abstracto estático carecería de sentido.

Estas dos combinaciones son errores de compilación.

Errores comunes

  • Olvidar abstract en la clase. Un método sin cuerpo dentro de una clase no abstracta no compilará — el compilador exige que la clase que lo contiene también sea abstract.
  • Intentar instanciarla. new Shape("x") es rechazado en tiempo de compilación. Instancia una subclase concreta en su lugar.
  • Dejar un método sin implementar en una subclase concreta. Si incluso un método abstracto heredado no tiene cuerpo, la subclase también debe declararse abstract.
  • Esperar herencia múltiple. Una clase solo puede extender un padre (abstracto o no). Si necesitas combinar varios contratos, recurre a las interfaces.

Un ejemplo completo

java— editable, runs on the server

Qué sigue

Ya has visto la versión de la abstracción que viene acompañada de estado y código compartido. La versión que prescinde de ambos — contrato puro, sin implementación — es la interfaz. Continúa con interfaces en Java.

Práctica

Práctica
Una clase extiende una clase abstracta pero no implementa todos sus métodos abstractos. ¿Qué ocurre?
Una clase extiende una clase abstracta pero no implementa todos sus métodos abstractos. ¿Qué ocurre?
Was this page helpful?