Surcharge de méthodes en Java
Surchargez les méthodes d'une classe parente en Java avec @Override et la distribution dynamique.
La surcharge (overriding) consiste à déclarer dans une sous-classe une méthode ayant la même signature qu'une méthode héritée, remplaçant ainsi la version parente. Combinée au polymorphisme, c'est le mécanisme qui permet d'appeler une méthode sur une référence de type parent et d'exécuter la bonne implémentation de la sous-classe.
Ce chapitre établit les règles : ce qui constitue une surcharge, ce qui n'en est pas une, ce que le compilateur autorise à modifier dans la version de la sous-classe, et ce que @Override apporte réellement.
À quoi ressemble une surcharge
Une méthode surcharge une méthode héritée lorsque toutes ces conditions sont réunies :
- Même nom.
- Même liste de paramètres (types et ordre, après effacement des génériques).
- Type de retour identique à celui du parent, ou un sous-type de celui-ci (retour covariant).
- Visibilité identique à celle du parent ou plus large.
- Non déclarée
final,staticouprivatedans le parent.
class Animal {
String speak() { return "(noise)"; }
}
class Cat extends Animal {
@Override
String speak() { return "meow"; }
}Il s'agit d'une surcharge valide. Appeler speak() sur un Cat renvoie "meow" ; l'appeler via une référence de type Animal pointant vers un Cat renvoie aussi "meow" — la distribution est déterminée par la classe de l'objet réel.
@Override — à utiliser toujours
@Override est une annotation qui indique au compilateur « j'ai l'intention de surcharger une méthode héritée. » Si elle ne surcharge pas réellement une méthode — mauvais nom, mauvais paramètres, mauvais type de retour — le compilateur génère une erreur :
class Cat extends Animal {
@Override
String Speak() { return "meow"; } // ERROR — no Speak() in Animal
}Sans l'annotation, ce code compilerait silencieusement comme une toute nouvelle méthode appelée Speak, et ((Animal)cat).speak() continuerait à renvoyer le "(noise)" du parent. L'annotation ne coûte rien et détecte toute une famille de bugs silencieux. Écrivez-la toujours sur les surcharges.
Les surcharges que vous écrirez le plus souvent ne concernent pas vos propres classes — elles portent sur des méthodes héritées de Object, la racine de toutes les classes. Surcharger toString(), equals(Object) et hashCode() permet à vos objets de s'afficher lisiblement, de se comparer par valeur et de se comporter correctement comme clés dans un HashMap ou un HashSet. @Override sur ces méthodes détecte le bug classique consistant à écrire equals(Cat other) (une surcharge de méthode) au lieu de equals(Object other) (la vraie surcharge).
Surcharge vs surcharge de méthode (overloading)
| Aspect | Surcharge (overriding) | Surcharge de méthode (overloading) |
|---|---|---|
| Où | Entre une sous-classe et une superclasse | Dans la même classe |
| Signature | Doit correspondre à celle du parent | Doit différer des autres |
| Distribution | À l'exécution, basée sur l'objet réel | À la compilation, basée sur les types d'arguments |
| Annotation | @Override | aucune |
Ces deux notions sont souvent confondues. La surcharge (overriding) concerne une méthode pour plusieurs types d'objets. La surcharge de méthode (overloading) concerne plusieurs méthodes pour un même type, choisie selon ce qu'on passe en argument.
Types de retour covariants
La sous-classe peut déclarer un type de retour plus étroit que celui du parent :
class Animal {
Animal makeBaby() { return new Animal(); }
}
class Cat extends Animal {
@Override
Cat makeBaby() { return new Cat(); } // returns Cat, not Animal — fine
}Il s'agit de la covariance — le type de retour de la surcharge est un sous-type de celui du parent. C'est sûr car tout appelant attendant un Animal de makeBaby() accepte volontiers un Cat. L'avantage est que les appelants qui savent qu'ils ont un Cat obtiennent directement un Cat sans transtypage :
Cat kitten = new Cat().makeBaby(); // no cast neededL'inverse (une sous-classe élargissant le type de retour) n'est pas autorisé — cela briserait les appelants qui attendaient le type plus étroit.
La visibilité ne peut que s'élargir
La surcharge doit être au moins aussi visible que la méthode du parent :
class A { public void hi() { } }
class B extends A { protected void hi() { } } // ERROR — reduces visibility
class C extends A { public void hi() { } } // okRéduire la visibilité signifierait qu'un appelant tenant une référence de type A pourrait appeler hi(), mais qu'après l'affectation A a = new B(), il ne le pourrait plus — ce qui briserait la substituabilité.
Les exceptions ne peuvent que se réduire
Si la méthode parente ne déclare pas d'exceptions vérifiées, la surcharge ne peut pas en déclarer non plus. Si le parent lève une exception vérifiée, la surcharge peut lever la même ou une sous-classe plus spécifique — mais pas une plus large :
class A {
void run() throws IOException { ... }
}
class B extends A {
@Override void run() throws FileNotFoundException { ... } // ok — FileNotFoundException extends IOException
}
class C extends A {
@Override void run() throws Exception { ... } // ERROR — too broad
}Les exceptions non vérifiées (sous-classes de RuntimeException) sont sans restriction — vous pouvez toujours les lever depuis une surcharge.
Ce qu'on ne peut pas surcharger
- Les méthodes
private. Elles ne sont pas visibles par la sous-classe, donc une méthode du même nom dans la sous-classe est simplement une nouvelle méthode. - Les méthodes
final. Marquées spécifiquement pour empêcher les surcharges. - Les méthodes
static. Une sous-classe peut déclarer une méthode statique avec le même nom et la même signature, mais cela s'appelle le masquage de méthode (method hiding), pas une surcharge. Il n'y a pas de polymorphisme — la JVM résout l'appel en fonction du type à la compilation de la référence :
class A { static String klass() { return "A"; } }
class B extends A { static String klass() { return "B"; } }
A a = new B();
System.out.println(a.klass()); // "A" — static, not polymorphicC'est l'un des rares cas où la règle « la distribution de méthode choisit la version de l'objet réel » ne s'applique pas.
Appeler le parent depuis la surcharge
Un modèle courant consiste à étendre plutôt qu'à remplacer le comportement du parent avec super.method(...) :
class Logger {
void log(String s) { System.out.println(s); }
}
class TimestampedLogger extends Logger {
@Override
void log(String s) {
super.log("[" + System.currentTimeMillis() + "] " + s);
}
}La surcharge décore le comportement du parent au lieu de le dupliquer. Ce sujet est abordé dans le chapitre sur le mot-clé super.
Un exemple concret
Et ensuite
Vous avez maintenant vu comment les sous-classes remplacent les méthodes parentes. L'idée suivante — l'abstraction — est l'autre face de la médaille : déclarer une méthode sans corps par défaut et obliger chaque sous-classe concrète à en fournir une. Continuez vers l'abstraction Java.