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 abstraite | Interface | |
|---|---|---|
| Constructeurs | Oui | Non |
| Champs d'instance | Oui | Non (uniquement des constantes public static final) |
| Corps de méthodes | Oui (en nombre quelconque) | Oui via default (à utiliser avec parcimonie) |
| Héritage | Simple — un seul parent | Multiple — plusieurs interfaces possibles |
| À utiliser quand | Les sous-classes partagent état et infrastructure | Les 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
privaterendrait la méthode invisible pour les sous-classes — elles ne pourraient pas la redéfinir, rendant ainsi l'abstraction non applicable.- Les méthodes
staticne 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
abstractsur la classe. Une méthode sans corps dans une classe non abstraite ne compilera pas — le compilateur exige que la classe englobante soit égalementabstract. - 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
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.