La surcharge de méthodes en Java
Définissez plusieurs méthodes avec le même nom mais des listes de paramètres différentes en Java pour offrir des API flexibles.
La surcharge de méthodes consiste à déclarer deux méthodes ou plus dans la même classe avec le même nom mais des listes de paramètres différentes. Le compilateur choisit laquelle appeler en fonction des types d'arguments que vous passez.
Vous avez déjà utilisé des méthodes surchargées sans le savoir. System.out.println(...) dispose de versions qui acceptent un int, un double, une String, un Object, un char[], etc. Elles se comportent toutes de manière similaire — afficher la valeur et un retour à la ligne — mais l'implémentation diffère selon le type.
Ce que signifie « liste de paramètres différente »
Deux surcharges doivent différer par leur arité (nombre de paramètres) ou les types de ces paramètres dans l'ordre. Elles ne peuvent pas différer uniquement par :
- Les noms des paramètres
- Le type de retour
- Le fait que les paramètres soient
final
public static int square(int n) { return n * n; }
// VALID overloads — different parameter types
public static double square(double n) { return n * n; }
public static long square(long n) { return n * n; }
// VALID overload — different arity
public static int sum(int a, int b) { return a + b; }
public static int sum(int a, int b, int c) { return a + b + c; }
// INVALID — only the return type differs
// public static long square(int n) { return (long) n * n; } // won't compileComment Java choisit une surcharge
Quand vous écrivez square(3), le compilateur procède dans cet ordre :
- Correspondance exacte. Existe-t-il une surcharge dont les paramètres correspondent exactement aux types des arguments ?
square(3)→square(int). Terminé. - Élargissement. Sinon, les arguments peuvent-ils être élargis (par exemple,
int → long,int → double) ? Choisir la surcharge qui nécessite le plus petit élargissement. - Autoboxing / unboxing. Sinon, essayer d'encapsuler ou de désencapsuler (
int ↔ Integer). - Varargs. En dernier recours, se rabattre sur une surcharge varargs (voir le chapitre sur les varargs).
Si deux surcharges sont à égalité à la même étape, l'appel est ambigu et ne compilera pas.
public static void show(int n) { System.out.println("int: " + n); }
public static void show(long n) { System.out.println("long: " + n); }
public static void show(double n) { System.out.println("double: " + n); }
show(3); // exact match → int
show(3L); // exact match → long
show(3.0); // exact match → double
show((short) 3); // widens short → int (closest), picks show(int)Ambiguïté
Lorsque le compilateur ne peut pas choisir entre deux surcharges tout aussi adaptées, vous obtenez une erreur :
public static void f(int a, long b) { /* ... */ }
public static void f(long a, int b) { /* ... */ }
f(1, 2); // ERROR: reference to f is ambiguousLes deux surcharges nécessitent l'élargissement d'un argument de int vers long. Aucune n'est « meilleure ». La solution consiste à lever l'ambiguïté au point d'appel avec un cast explicite — f(1, 2L) ou f(1L, 2) — ou à ajouter une troisième surcharge f(int, int) qui gère ce cas exactement.
Surcharge avec des types objet
Les types référence se surchargent de la même manière, mais avec des relations de sous-type au lieu de l'élargissement :
public static void log(Object o) { System.out.println("Object: " + o); }
public static void log(String s) { System.out.println("String: " + s); }
log("hello"); // exact match → String
log(42); // autobox to Integer, then Integer is-a Object → log(Object)
log((Object) "hi"); // forces the Object overloadSi vous passez une String et que la seule surcharge accepte un Object, Java fonctionne correctement — String est un Object. Mais s'il existe une surcharge plus spécifique, Java la préfère.
Quand la surcharge est utile
L'intérêt de la surcharge est d'offrir aux appelants une API propre et naturelle selon les types. Deux patterns reviennent le plus souvent :
Valeurs par défaut. Une surcharge courte appelle la longue avec des valeurs par défaut sensées :
public static void greet(String name) {
greet(name, 1); // delegate
}
public static void greet(String name, int times) {
for (int i = 0; i < times; i++) {
System.out.println("Hello, " + name);
}
}Convertisseurs pratiques. Différents types d'entrée, même opération logique :
public static int lengthOf(String s) { return s == null ? 0 : s.length(); }
public static int lengthOf(int[] xs) { return xs == null ? 0 : xs.length; }
public static int lengthOf(int n) { return Integer.toString(n).length(); }L'appelant écrit lengthOf(x) sans réfléchir ; le compilateur route vers le bon corps de méthode.
Quand ne pas surcharger
Si deux surcharges feraient des choses fondamentalement différentes, donnez-leur des noms différents à la place. Le lecteur de format(x, y) ne devrait pas avoir à vérifier quelle surcharge a été choisie pour comprendre ce que fait l'appel. Les surcharges doivent être des variations de la même idée, pas des idées différentes partageant un nom.
Surcharge vs. redéfinition
Ces deux termes se ressemblent mais résolvent des problèmes différents, et les confondre est la source d'erreurs la plus courante.
- La surcharge (overloading), c'est même nom, listes de paramètres différentes, au sein d'une même classe. La méthode exécutée est décidée par le compilateur, en regardant uniquement les types statiques (déclarés) des arguments. C'est ce qu'on appelle la liaison statique (à la compilation).
- La redéfinition (overriding), c'est même nom, même liste de paramètres, dans une sous-classe — elle remplace une méthode héritée. La méthode exécutée est décidée par la JVM à l'exécution, en fonction du type réel de l'objet. C'est la liaison dynamique (à l'exécution).
Object x = "hello";
log(x); // calls log(Object), NOT log(String) — chosen from x's DECLARED typeMême si x contient une String à l'exécution, le compilateur ne voit que Object x, il choisit donc la surcharge Object. La sélection de surcharge ne regarde jamais le type à l'exécution — c'est le rôle de la redéfinition. Voir la redéfinition de méthodes pour le côté exécution.
Un exemple complet
Et ensuite
La surcharge permet à un nom de pointer vers plusieurs méthodes. Parfois on veut qu'une seule méthode s'appelle elle-même — en résolvant un problème en découpant un morceau et en demandant à une version plus petite du même problème de s'en charger. C'est la récursion.