W3docs

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, static ou private dans 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.

Note

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)

AspectSurcharge (overriding)Surcharge de méthode (overloading)
Entre une sous-classe et une superclasseDans la même classe
SignatureDoit correspondre à celle du parentDoit 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@Overrideaucune

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 needed

L'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() { } }   // ok

Ré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 polymorphic

C'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

java— editable, runs on the server

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.

Exercices

Pratique
Dans une sous-classe, pourquoi vaut-il la peine d'écrire @Override sur chaque surcharge même si le code compilerait sans ?
Dans une sous-classe, pourquoi vaut-il la peine d'écrire @Override sur chaque surcharge même si le code compilerait sans ?
Was this page helpful?