En Java, l’héritage peut simplifier une architecture ou, au contraire, la rendre fragile si on l’utilise trop vite. Le point clé est de comprendre ce que le mot-clé `extends` autorise réellement, ce qu’il refuse, et dans quels cas il vaut mieux lui préférer `implements` ou la composition. Je vais aller droit au but: syntaxe, comportement, limites, erreurs fréquentes et critères de choix.
L’essentiel à retenir sur l’héritage avec `extends` en Java
- Une classe enfant récupère l’API visible de sa classe mère, mais pas une copie magique de tout son fonctionnement interne.
- En Java, une classe n’hérite que d’une seule autre classe; c’est une limite structurante, pas un détail de syntaxe.
- `super` sert à appeler explicitement le parent, notamment dans un constructeur ou une méthode surchargée.
- `extends` existe aussi pour les interfaces et les bornes génériques, mais ce n’est pas le même usage.
- Quand la relation n’est pas un vrai « est un », la composition est souvent plus robuste que l’héritage.
Ce que `extends` change réellement dans une classe Java
Quand j’utilise `extends` sur une classe, je crée une relation de parent à enfant: la sous-classe hérite des membres accessibles de la classe mère et peut les enrichir ou les redéfinir. La documentation Oracle rappelle qu’une classe Java n’a qu’une seule superclasse directe, ce qui impose déjà une discipline de conception: on ne peut pas empiler plusieurs bases au hasard.
Ce point est important, parce que l’héritage ne copie pas le code ligne par ligne. Il construit une hiérarchie de types: un objet de sous-classe peut être utilisé là où le type parent est attendu, ce qui est pratique pour le polymorphisme. En bas de cette hiérarchie, toute classe Java hérite indirectement de `Object`, ce qui explique pourquoi des méthodes comme `toString()` ou `equals()` existent partout. En revanche, les éléments `private` du parent ne deviennent pas directement accessibles, et les comportements implicites du parent restent liés à ses propres règles internes.
Autrement dit, l’héritage sert à partager une identité fonctionnelle, pas à faire du copier-coller orienté objet. Une fois cette base posée, la vraie question devient: comment lire et écrire la syntaxe sans se tromper sur les effets réels?

Lire la syntaxe sans se tromper sur le constructeur et `super`
La forme de base est simple, mais c’est souvent là que les erreurs commencent. Voici un exemple minimal:
class Vehicle {
protected int speed;
Vehicle(int speed) {
this.speed = speed;
}
void accelerate(int delta) {
speed += delta;
}
}
class Car extends Vehicle {
private final String model;
Car(String model, int speed) {
super(speed);
this.model = model;
}
@Override
void accelerate(int delta) {
super.accelerate(delta);
System.out.println(model + " roule maintenant à " + speed + " km/h");
}
}Ici, `Car` hérite de `Vehicle`, mais elle personnalise deux choses: son état propre avec `model`, et son comportement avec la méthode `accelerate()`. Le mot-clé `super` est utile dans deux cas très concrets: appeler le constructeur du parent et réutiliser une implémentation parent avant ou après l’ajout de logique spécifique.
Le piège classique, c’est d’oublier qu’un constructeur de sous-classe doit initialiser correctement la partie héritée. Si la superclasse n’expose pas de constructeur sans argument, vous devez appeler explicitement `super(...)` en premier dans le constructeur enfant. C’est une règle simple, mais elle évite des hiérarchies cassées dès la compilation. Avec cette syntaxe en tête, il vaut mieux regarder ce que l’héritage apporte vraiment au projet, et ce qu’il peut lui coûter.
Le deuxième usage de `extends` en génériques
Le mot-clé ne sert pas seulement à relier deux classes. En Java, il apparaît aussi dans les génériques pour exprimer une borne supérieure, par exemple `T extends Number`. Ici, on ne dit pas qu’une classe enfant hérite d’une classe mère; on dit qu’un type générique doit être `Number` ou un sous-type de `Number`.
class Stats {
double average(T a, T b) {
return (a.doubleValue() + b.doubleValue()) / 2.0;
}
} La nuance compte beaucoup. Dans ce cas, `extends` restreint les types autorisés pour garantir que certaines méthodes existent, comme `doubleValue()` dans l’exemple. La documentation Oracle distingue bien ces usages: l’un relève de l’héritage de classes, l’autre de la contrainte de type. Je vois encore des confusions là-dessus dans des bases de code réelles, surtout quand les génériques arrivent tard dans un projet.
Si vous comprenez cette séparation, vous évitez de mélanger une hiérarchie objet avec une borne de type. C’est le bon moment pour comparer les bénéfices et les limites de l’héritage lui-même.
Ce que l’héritage vous apporte et ce qu’il vous enlève
J’aime résumer l’héritage comme un outil de réutilisation structurée: il réduit les répétitions, mais il augmente le niveau de couplage entre classes. Dans un petit projet, ce compromis peut être très rentable. Dans un système qui évolue vite, il faut être plus prudent.
| Ce que vous gagnez | Ce que vous payez |
|---|---|
| Réutilisation de comportement commun | Dépendance forte à la classe mère |
| Polymorphisme simple et lisible | Hiérarchies parfois rigides à faire évoluer |
| Code métier centralisé dans une base commune | Risque de « classe fourre-tout » si on force trop la conception |
| API plus expressive quand la relation est vraiment un « est un » | Moins de liberté qu’avec des objets composés ou des services séparés |
Le point décisif, pour moi, n’est pas de savoir si l’héritage est élégant en théorie. C’est de savoir s’il reste stable quand le code change, quand les besoins métier se déplacent, et quand la base s’agrandit. C’est aussi là qu’il faut le comparer à `implements` et à la composition, parce que ces trois options ne règlent pas le même problème.
Comparer `extends`, `implements` et la composition
La documentation Oracle rappelle aussi que les interfaces peuvent étendre plusieurs interfaces, alors qu’une classe ne peut étendre qu’une seule classe. Cette différence change profondément la manière de concevoir une architecture Java.
| Approche | Ce qu’elle exprime | Forces | Limite principale |
|---|---|---|---|
extends sur une classe |
Une vraie relation parent/enfant | Réutilisation et polymorphisme directs | Un seul parent possible |
implements |
Respect d’un contrat | Découplage plus fort, flexibilité | Pas de réutilisation d’état concrète |
| Composition | Une classe utilise un autre objet | Évolution plus sûre, tests plus simples | Un peu plus de câblage au départ |
En pratique, j’utilise `extends` quand je veux dire « cette classe est une spécialisation de celle-ci ». J’utilise `implements` quand je veux dire « cette classe respecte ce contrat ». Et je choisis la composition quand je veux réutiliser un comportement sans hériter de toutes les décisions de design du parent. Cette distinction paraît académique au début, mais elle évite beaucoup de refontes inutiles.
Les erreurs que je vois le plus souvent
La plupart des problèmes autour de l’héritage ne viennent pas de la syntaxe. Ils viennent d’une mauvaise lecture du rôle de la superclasse. Voici les pièges qui reviennent sans cesse dans les projets Java.
- Forcer une relation qui n’est pas un vrai « est un » - si la sous-classe n’est pas réellement une spécialisation du parent, la hiérarchie finit par mentir.
- Oublier `@Override` - l’annotation ne change pas le comportement, mais elle évite de croire qu’on surcharge une méthode alors qu’on en crée une autre par erreur.
- Supposer que tout est hérité - les membres `private` ne sont pas accessibles directement, et certains comportements du parent restent encapsulés.
- Mélanger surcharge et redéfinition - ce n’est pas la même chose, et la différence est décisive pour le polymorphisme.
- Négliger les constructeurs du parent - un `super(...)` mal placé ou oublié bloque rapidement la compilation.
- Abuser des classes profondes - plus la chaîne d’héritage est longue, plus le raisonnement devient coûteux pour une équipe.
- Confondre héritage et partage d’implémentation - parfois, on veut juste réutiliser un morceau de logique; une composition suffit alors largement.
Je vois aussi un faux ami plus subtil: les types génériques. Par exemple, croire qu’un `List
Quand je choisis l’héritage dans un projet Java
Je choisis l’héritage quand trois conditions sont réunies: il existe une vraie base commune, la sous-classe garde la même intention métier, et la classe mère définit un contrat assez stable pour ne pas se casser au premier changement. Dans ce scénario, l’héritage simplifie le code au lieu de l’alourdir.
Les cas les plus défendables sont souvent les suivants:
- une famille d’objets du même domaine, avec des caractéristiques partagées et une variation claire;
- des frameworks ou bibliothèques qui s’appuient sur une classe de base conçue pour être étendue;
- des scénarios où la sous-classe peut réellement être substituée au parent sans surprise;
- des règles transverses communes, à condition que la classe mère reste petite et lisible.
En revanche, si vous sentez que vous ajoutez des méthodes abstraites pour « faire rentrer » deux comportements différents dans la même hiérarchie, c’est souvent le signal d’alarme le plus fiable. L’héritage doit clarifier le modèle, pas le maquiller. Et c’est précisément ce point qui me pousse à terminer avec une règle simple pour écrire des hiérarchies durables.
Écrire des hiérarchies qui restent lisibles dans six mois
Ma règle la plus utile est très simple: si la hiérarchie vous oblige à expliquer trop de cas particuliers, elle est déjà trop lourde. Une bonne classe mère reste petite, stable et centrée sur un vrai socle commun. Une bonne sous-classe ajoute une variation nette, pas une pile d’exceptions locales.
Je recommande aussi de vérifier trois choses avant d’introduire un `extends`:
- la relation métier est-elle vraiment naturelle et durable?
- la classe mère peut-elle évoluer sans casser les enfants?
- la composition ou une interface ne ferait-elle pas le travail avec moins de risques?
Quand ces réponses sont claires, l’héritage devient un outil propre et lisible. Quand elles sont floues, je préfère reculer d’un pas et garder une architecture plus souple. C’est souvent ce choix prudent qui fait la différence entre un code qui s’étend bien et un code qu’on redoute de toucher.