W3docs

Classes abstraites en Java

Définissez des implémentations partielles en Java avec des classes et méthodes abstraites que les sous-classes doivent compléter.

Une classe abstraite est une classe qui ne peut pas être instanciée directement. Elle existe pour être étendue. Elle peut combiner des méthodes concrètes (avec un corps) et des méthodes abstraites (sans corps, que la sous-classe doit implémenter) — cette combinaison est ce qui la distingue à la fois d'une classe ordinaire et d'une interface.

Cette page explique comment déclarer une classe abstraite, comment les sous-classes la complètent, pourquoi les classes abstraites peuvent contenir un état, le patron de conception méthode template, et comment choisir entre une classe abstraite et une interface.

Utilisez une classe abstraite lorsque les sous-classes concrètes doivent partager un état et une infrastructure, et pas seulement un contrat d'API. Si tout ce dont vous avez besoin est un contrat, une interface est plus adaptée. Les classes abstraites s'appuient sur l'héritage et le polymorphisme, il est donc utile de maîtriser ces notions au préalable.

Déclarer une classe abstraite

Ajoutez abstract à l'en-tête de la classe. Ajoutez abstract à toute méthode sans corps :

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

Quelques conséquences :

  • new Shape("circle") est une erreur de compilation — les classes abstraites ne peuvent pas être instanciées.
  • Une sous-classe qui n'implémente pas toutes les méthodes abstraites héritées doit elle-même être déclarée abstract. On peut avoir des sous-classes abstraites de classes abstraites.
  • Une classe abstraite peut avoir un constructeur — les sous-classes l'appellent avec super(...) comme pour tout parent ordinaire.

Implémenter les méthodes abstraites

Une sous-classe concrète doit fournir un corps pour chaque méthode abstraite héritée :

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

Désormais new Circle(2) fonctionne, et describe() (héritée de Shape) rappelle la méthode area() de la sous-classe via la répartition dynamique.

Les classes abstraites peuvent contenir un état

C'est la principale raison de choisir une classe abstraite plutôt qu'une interface. Le parent peut déclarer des champs, écrire un constructeur qui les initialise, et proposer des méthodes qui opèrent sur cet état partagé :

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
}

Chaque gestionnaire concret possède un path ; le parent le stocke et l'expose ; chaque sous-classe n'écrit que la logique spécifique à chaque requête. Les interfaces ne peuvent pas faire cela seules (elles n'ont pas de champs d'instance).

Mélanger méthodes abstraites et concrètes — la méthode template

Un patron courant : la classe abstraite implémente le flux global sous forme de méthode concrète et laisse les points de variation abstraits. Les sous-classes ne remplissent que les parties qui diffèrent :

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() exécute le template du parent, qui rappelle les méthodes brew et addCondiments de Tea via le polymorphisme. L'ajout d'une sous-classe Coffee ne nécessite que les deux méthodes abstraites.

C'est le patron méthode template et c'est la raison la plus courante de choisir une classe abstraite.

Abstrait vs final

abstract et final sont des opposés et le compilateur le fait respecter :

  • abstract class — doit être sous-classée.
  • final class — ne peut pas être sous-classée.

Même chose pour les méthodes : les méthodes abstract doivent être redéfinies ; les méthodes final ne peuvent pas l'être. Écrire les deux à la fois est une erreur de compilation.

Classe abstraite vs interface

Classe abstraiteInterface
ConstructeursOuiNon
Champs d'instanceOuiNon (uniquement des constantes public static final)
Corps de méthodesOui (en nombre quelconque)Oui via default (à utiliser avec parcimonie)
HéritageSimple — un seul parentMultiple — plusieurs interfaces possibles
À utiliser quandLes sous-classes partagent état et infrastructureLes sous-classes ne partagent qu'un contrat d'API

Une règle pratique : commencez par une interface. Passez à (ou ajoutez) une classe abstraite uniquement si vous constatez que du code partagé s'accumule entre les implémentations. Les méthodes default sur les interfaces de Java 8+ ont empiété sur certains domaines autrefois réservés aux classes abstraites, mais le cas d'usage de l'« état mutable partagé » reste le territoire des classes abstraites.

Les méthodes abstraites ne peuvent pas être private ni static

  • private rendrait la méthode invisible pour les sous-classes — elles ne pourraient pas la redéfinir, rendant ainsi l'abstraction non applicable.
  • Les méthodes static ne sont pas réparties dynamiquement — elles ne peuvent pas être redéfinies, seulement masquées — donc une méthode statique abstraite n'aurait aucun sens.

Ces deux combinaisons sont des erreurs de compilation.

Erreurs courantes

  • Oublier abstract sur la classe. Une méthode sans corps dans une classe non abstraite ne compilera pas — le compilateur exige que la classe englobante soit également abstract.
  • Tenter de l'instancier. new Shape("x") est rejeté à la compilation. Instanciez plutôt une sous-classe concrète.
  • Laisser une méthode non implémentée dans une sous-classe concrète. Si ne serait-ce qu'une méthode abstraite héritée n'a pas de corps, la sous-classe doit aussi être déclarée abstract.
  • S'attendre à un héritage multiple. Une classe ne peut étendre qu'un seul parent (abstrait ou non). Si vous devez combiner plusieurs contrats, utilisez les interfaces.

Exemple complet

java— editable, runs on the server

La suite

Vous avez maintenant découvert la forme d'abstraction qui inclut état et code partagé. La forme qui abandonne les deux — contrat pur, sans implémentation — est l'interface. Continuez vers les interfaces Java.

Entraînement

Pratique
Une classe étend une classe abstraite mais n'implémente pas toutes ses méthodes abstraites. Que se passe-t-il ?
Une classe étend une classe abstraite mais n'implémente pas toutes ses méthodes abstraites. Que se passe-t-il ?
Was this page helpful?