W3docs

Classi Astratte in Java

Definisci implementazioni parziali in Java con classi e metodi astratti che le sottoclassi devono completare.

Una classe astratta è una classe che non può essere istanziata direttamente. Esiste per essere estesa. Può combinare metodi concreti (con corpo) e metodi astratti (senza corpo, che la sottoclasse deve implementare) — questa combinazione la distingue sia da una classe normale che da un'interfaccia.

Questa pagina illustra come dichiarare una classe astratta, come le sottoclassi la completano, perché le classi astratte possono contenere stato, il pattern template method e come scegliere tra una classe astratta e un'interfaccia.

Usa una classe astratta quando le sottoclassi concrete devono condividere stato e infrastruttura, non solo un contratto API. Se hai bisogno solo di un contratto, un'interfaccia è la scelta migliore. Le classi astratte si basano sull'ereditarietà e il polimorfismo, quindi è utile conoscere bene questi concetti prima.

Dichiarare una classe astratta

Aggiungi abstract all'intestazione della classe. Aggiungi abstract a qualsiasi metodo che non ha corpo:

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();
  }
}

Da questo derivano alcune conseguenze:

  • new Shape("circle") è un errore di compilazione — le classi astratte non possono essere istanziate.
  • Una sottoclasse che non implementa tutti i metodi astratti ereditati deve essa stessa essere dichiarata abstract. Possono esistere sottoclassi astratte di classi astratte.
  • Una classe astratta può avere un costruttore — le sottoclassi lo invocano con super(...) proprio come un normale genitore.

Implementare i metodi astratti

Una sottoclasse concreta deve fornire un corpo per ogni metodo astratto ereditato:

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; }
}

Ora new Circle(2) funziona, e describe() (ereditato da Shape) chiama il metodo area() della sottoclasse tramite dispatch dinamico.

Le classi astratte possono contenere stato

Questo è il motivo principale per preferire una classe astratta a un'interfaccia. Il genitore può dichiarare campi, scrivere un costruttore che li inizializza e offrire metodi che operano su quello stato condiviso:

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
}

Ogni handler concreto ha un path; il genitore lo memorizza e lo espone; ogni sottoclasse scrive solo la logica specifica per la richiesta. Le interfacce non possono fare questo da sole (non hanno campi di istanza).

Combinare metodi astratti e concreti — il template method

Un pattern comune: la classe astratta implementa il flusso generale come metodo concreto e lascia astratti i punti di variazione. Le sottoclassi completano solo le parti che differiscono:

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() esegue il template del genitore, che richiama brew e addCondiments di Tea tramite polimorfismo. Aggiungere una sottoclasse Coffee richiede solo i due metodi astratti.

Questo è il pattern template method ed è il motivo più comune per scegliere una classe astratta.

Astratto vs finale

abstract e final sono opposti e il compilatore lo impone:

  • abstract class — deve essere estesa.
  • final class — non deve essere estesa.

Lo stesso vale per i metodi: i metodi abstract devono essere sovrascritti; i metodi final non possono esserlo. Scriverli entrambi insieme è un errore di compilazione.

Classe astratta vs interfaccia

Classe astrattaInterfaccia
CostruttoriNo
Campi di istanzaNo (solo costanti public static final)
Corpi dei metodiSì (qualsiasi numero)Sì tramite default (usato con parsimonia)
EreditarietàSingola — un solo genitoreMultipla — più interfacce
Quando usareLe sottoclassi condividono stato e infrastrutturaLe sottoclassi condividono solo un contratto API

Una regola pratica: inizia con un'interfaccia. Passa a (o aggiungi) una classe astratta solo se riscontri l'accumulo di codice condiviso tra le implementazioni. I metodi default di Java 8+ sulle interfacce hanno eroso parte del territorio che un tempo apparteneva alle classi astratte, ma il caso d'uso dello "stato mutabile condiviso" rimane territorio delle classi astratte.

I metodi astratti non possono essere private o static

  • private renderebbe il metodo invisibile alle sottoclassi — non potrebbero sovrascriverlo, rendendo inapplicabile l'astrazione.
  • I metodi static non vengono dispatchati dinamicamente — non possono essere sovrascritti, solo nascosti — quindi un metodo astratto statico sarebbe privo di senso.

Queste due combinazioni sono errori di compilazione.

Errori comuni

  • Dimenticare abstract sulla classe. Un metodo senza corpo all'interno di una classe non astratta non compila — il compilatore richiede che anche la classe contenitrice sia abstract.
  • Tentare di istanziarla. new Shape("x") viene rifiutato in fase di compilazione. Istanzia invece una sottoclasse concreta.
  • Lasciare un metodo non implementato in una sottoclasse concreta. Se anche un solo metodo astratto ereditato non ha un corpo, la sottoclasse deve anch'essa essere dichiarata abstract.
  • Aspettarsi l'ereditarietà multipla. Una classe può estendere un solo genitore (astratto o meno). Se devi combinare più contratti, usa le interfacce.

Un esempio pratico

java— editable, runs on the server

Cosa c'è dopo

Hai ora visto la versione dell'astrazione che include stato e codice condiviso. La versione che elimina entrambi — puro contratto, nessuna implementazione — è l'interfaccia. Continua con le interfacce Java.

Pratica

Pratica
Una classe estende una classe astratta ma non implementa tutti i suoi metodi astratti. Cosa succede?
Una classe estende una classe astratta ma non implementa tutti i suoi metodi astratti. Cosa succede?
Was this page helpful?