Table of Contents
Introduction
La programmation orientee objet (POO) est un paradigme de developpement logiciel qui permet de creer des applications complexes en utilisant des objets et leurs interactions. Dans ce cadre, les methodes sont un element cle du langage Java pour definir le comportement d’un objet.
Le polymorphisme est l’un des quatre piliers fondamentaux de la POO, aux cotes de l’encapsulation, l’heritage et l’abstraction. En Java, le polymorphisme se manifeste principalement sous deux formes distinctes :
- Method Overloading (polymorphisme statique ou de compilation)
- Method Overriding (polymorphisme dynamique ou d’execution)
Ces deux concepts, bien que portant des noms similaires, ont des implications fondamentalement differentes dans la conception et l’execution de vos programmes Java. Comprendre leurs differences est essentiel pour :
- Concevoir des APIs intuitives - L’overloading permet de proposer plusieurs signatures pour une meme operation
- Implementer l’heritage correctement - L’overriding permet de specialiser le comportement des sous-classes
- Eviter les bugs subtils - Une mauvaise comprehension peut mener a des comportements inattendus
- Optimiser les performances - Le choix entre statique et dynamique impacte l’execution
Dans cet article, nous allons explorer en profondeur ces deux concepts, avec des exemples de code concrets, des bonnes pratiques et les pieges courants a eviter.
Methode Overloading (Surcharge)
L’overloading de methode, ou surcharge, est une technique qui permet d’avoir plusieurs methodes avec le meme nom mais avec des signatures differentes. La signature d’une methode en Java est definie par :
- Le nombre de parametres
- Le type des parametres
- L’ordre des parametres
Important : Le type de retour ne fait PAS partie de la signature. Vous ne pouvez pas surcharger une methode uniquement en changeant son type de retour.
Exemple Basique
public class Displayer {
public void displayName(String firstName) {
System.out.println("Name is: " + firstName);
}
public void displayName(String firstName, String lastName) {
System.out.println("Name is: " + firstName + " " + lastName);
}
}
Dans cet exemple, nous avons deux methodes displayName qui prennent respectivement un seul parametre String et deux parametres String. Le compilateur decide quelle methode executer en fonction des arguments passes.
Exemple Avance avec Differents Types
public class Calculator {
// Surcharge par nombre de parametres
public int add(int a, int b) {
return a + b;
}
public int add(int a, int b, int c) {
return a + b + c;
}
// Surcharge par type de parametres
public double add(double a, double b) {
return a + b;
}
// Surcharge par ordre de parametres
public String add(String prefix, int number) {
return prefix + number;
}
public String add(int number, String suffix) {
return number + suffix;
}
}
// Utilisation
Calculator calc = new Calculator();
System.out.println(calc.add(5, 3)); // Appelle add(int, int) -> 8
System.out.println(calc.add(5, 3, 2)); // Appelle add(int, int, int) -> 10
System.out.println(calc.add(5.5, 3.2)); // Appelle add(double, double) -> 8.7
System.out.println(calc.add("ID-", 123)); // Appelle add(String, int) -> "ID-123"
Resolution au Moment de la Compilation
L’overloading est aussi appele polymorphisme statique car la decision de quelle methode appeler est prise au moment de la compilation (compile-time binding). Le compilateur Java analyse les types des arguments et selectionne la methode correspondante.
Methode Overriding (Redefinition)
L’overriding de methode, ou redefinition, est une technique qui permet a une classe enfant de redefinir une methode heritee de sa classe parent. Contrairement a l’overloading, l’overriding necessite :
- Une relation d’heritage entre les classes
- La meme signature de methode (nom + parametres)
- L’annotation
@Override(recommandee)
Exemple Basique avec Classes Abstraites
public abstract class Shape {
public abstract Double area();
}
public class Circle extends Shape {
private Double radius = 5.0;
@Override
public Double area() {
return Math.PI * radius * radius;
}
}
public class Rectangle extends Shape {
private Double width = 4.0;
private Double height = 6.0;
@Override
public Double area() {
return width * height;
}
}
Exemple Complet avec Polymorphisme Dynamique
public class Animal {
public void makeSound() {
System.out.println("L'animal fait un son");
}
public void move() {
System.out.println("L'animal se deplace");
}
}
public class Dog extends Animal {
@Override
public void makeSound() {
System.out.println("Le chien aboie : Wouf!");
}
@Override
public void move() {
System.out.println("Le chien court a quatre pattes");
}
}
public class Bird extends Animal {
@Override
public void makeSound() {
System.out.println("L'oiseau chante : Cui-cui!");
}
@Override
public void move() {
System.out.println("L'oiseau vole dans le ciel");
}
}
// Demonstration du polymorphisme dynamique
public class Main {
public static void main(String[] args) {
Animal[] animals = {new Dog(), new Bird(), new Animal()};
for (Animal animal : animals) {
animal.makeSound(); // La methode appelee depend du type reel
animal.move();
System.out.println("---");
}
}
}
Sortie :
Le chien aboie : Wouf!
Le chien court a quatre pattes
---
L'oiseau chante : Cui-cui!
L'oiseau vole dans le ciel
---
L'animal fait un son
L'animal se deplace
---
Resolution au Moment de l’Execution
L’overriding est appele polymorphisme dynamique car la decision de quelle methode appeler est prise au moment de l’execution (runtime binding). La JVM examine le type reel de l’objet, pas le type declare de la reference.
Tableau Comparatif : Overloading vs Overriding
| Critere | Overloading | Overriding |
|---|---|---|
| Moment de resolution | Compilation (statique) | Execution (dynamique) |
| Signature | Differente | Identique |
| Heritage requis | Non | Oui |
| Annotation | Aucune | @Override recommandee |
| Type de retour | Peut varier | Identique ou covariant |
| Modificateur d’acces | Peut varier | Meme ou plus permissif |
| Methodes statiques | Oui | Non (hiding) |
| Methodes finales | Oui | Non |
| Methodes privees | Oui | Non |
| Performances | Plus rapide (lie au compile) | Leger overhead (virtual call) |
Exemple Combinant les Deux Concepts
public class Person {
// Methodes surchargees (overloading)
public void sayHello() {
System.out.println("Bonjour !");
}
public void sayHello(String name) {
System.out.println("Bonjour " + name + " !");
}
public void sayHello(String name, String title) {
System.out.println("Bonjour " + title + " " + name + " !");
}
}
public class Student extends Person {
// Methode redifinie (overriding)
@Override
public void sayHello() {
System.out.println("Salut ! Je suis etudiant.");
}
// Cette methode herite des surcharges du parent
// sayHello(String) et sayHello(String, String) sont disponibles
}
// Utilisation
Student student = new Student();
student.sayHello(); // "Salut ! Je suis etudiant." (overriding)
student.sayHello("Marie"); // "Bonjour Marie !" (inherited overloading)
student.sayHello("Dupont", "Professeur"); // "Bonjour Professeur Dupont !" (inherited)
Bonnes Pratiques
Pour l’Overloading
- Gardez une coherence semantique - Toutes les versions surchargees doivent effectuer la meme operation logique
// BON : Toutes les versions calculent une surface
public double calculateArea(double radius) { ... }
public double calculateArea(double width, double height) { ... }
public double calculateArea(double a, double b, double c) { ... }
// MAUVAIS : Operations semantiquement differentes
public void process(String data) { /* sauvegarde */ }
public void process(int count) { /* suppression */ } // Confus!
- Utilisez des valeurs par defaut via telescoping
public void log(String message) {
log(message, "INFO");
}
public void log(String message, String level) {
log(message, level, System.currentTimeMillis());
}
public void log(String message, String level, long timestamp) {
// Implementation complete
System.out.printf("[%s] %d: %s%n", level, timestamp, message);
}
- Privilegiez les varargs pour les listes homogenes
public int sum(int... numbers) {
return Arrays.stream(numbers).sum();
}
// Permet: sum(1), sum(1,2), sum(1,2,3,4,5)
Pour l’Overriding
- Toujours utiliser @Override
@Override // Le compilateur verifiera que vous redefinissez bien une methode
public String toString() {
return "MyClass{...}";
}
- Appelez super() quand necessaire
public class EnhancedLogger extends BasicLogger {
@Override
public void log(String message) {
String enriched = addMetadata(message);
super.log(enriched); // Reutilise la logique parent
}
}
- Respectez le contrat Liskov Substitution Principle (LSP)
// La classe enfant doit pouvoir remplacer la classe parent sans casser le code
public class Bird {
public void fly() { System.out.println("Flying"); }
}
// MAUVAIS : Un pingouin ne vole pas!
public class Penguin extends Bird {
@Override
public void fly() {
throw new UnsupportedOperationException("Penguins can't fly");
}
}
Pieges Courants a Eviter
Piege 1 : Confusion entre Overloading et Overriding
public class Parent {
public void display(Object obj) {
System.out.println("Parent: Object");
}
}
public class Child extends Parent {
// ATTENTION: Ceci est de l'OVERLOADING, pas de l'overriding!
public void display(String str) {
System.out.println("Child: String");
}
}
Child child = new Child();
child.display("test"); // "Child: String"
child.display(new Object()); // "Parent: Object"
Parent parent = new Child();
parent.display("test"); // "Parent: Object" - Surprise! La reference est Parent
Piege 2 : Autoboxing et Ambiguite
public class BoxingTrap {
public void process(int value) {
System.out.println("Primitive int");
}
public void process(Integer value) {
System.out.println("Wrapper Integer");
}
public void process(Object value) {
System.out.println("Object");
}
}
BoxingTrap trap = new BoxingTrap();
trap.process(5); // "Primitive int"
trap.process(Integer.valueOf(5)); // "Wrapper Integer"
trap.process(null); // ERREUR de compilation! Ambiguite entre Integer et Object
Piege 3 : Methodes Statiques et Hiding
public class Parent {
public static void staticMethod() {
System.out.println("Parent static");
}
}
public class Child extends Parent {
// Ceci est du HIDING, pas de l'overriding!
public static void staticMethod() {
System.out.println("Child static");
}
}
Parent.staticMethod(); // "Parent static"
Child.staticMethod(); // "Child static"
Parent ref = new Child();
ref.staticMethod(); // "Parent static" - Resolu au compile-time!
Piege 4 : Covariance du Type de Retour
public class Animal {
public Animal reproduce() {
return new Animal();
}
}
public class Dog extends Animal {
@Override
public Dog reproduce() { // Covariant return type - OK depuis Java 5
return new Dog();
}
}
// Mais attention:
public class Cat extends Animal {
@Override
public String reproduce() { // ERREUR! String n'est pas un sous-type d'Animal
return "kitten";
}
}
Conclusion
L’overloading et l’overriding sont deux mecanismes fondamentaux du polymorphisme en Java qui, bien que portant des noms similaires, servent des objectifs tres differents :
- L’overloading (surcharge) permet de creer des APIs flexibles avec plusieurs signatures pour une meme operation, resolue au moment de la compilation
- L’overriding (redefinition) permet de specialiser le comportement des sous-classes, resolu au moment de l’execution
Points cles a retenir :
- Utilisez l’overloading pour offrir des variantes d’une meme operation
- Utilisez l’overriding pour implementer le polymorphisme d’heritage
- Annotez toujours avec
@Overridepour detecter les erreurs au compile-time - Soyez vigilant sur la resolution statique vs dynamique
- Testez les cas limites avec les types references vs types reels
En maitrisant ces deux concepts, vous serez capable de concevoir des architectures Java elegantes, extensibles et maintenables.
Pour Aller Plus Loin
- Explorez les methodes par defaut dans les interfaces (Java 8+)
- Etudiez le pattern Strategy qui exploite le polymorphisme dynamique
- Decouvrez les generics pour un polymorphisme parametrique
- Apprenez le pattern Template Method combinant overriding et heritage
In-Article Ad
Dev Mode
Tags
Mahmoud DEVO
Senior Full-Stack Developer
I'm a passionate full-stack developer with 10+ years of experience building scalable web applications. I write about Vue.js, Node.js, PostgreSQL, and modern DevOps practices.
Enjoyed this article?
Subscribe to get more tech content delivered to your inbox.
Related Articles
Interfaces en Java : Guide Pratique avec Methodes Par Defaut et Heritage
Maitrisez les interfaces Java : definition, implementation, heritage et methodes par defaut. Exemples pratiques pour creer du code modulaire et reutilisable.
Serialization en Java : Gestion des Mises a Jour de Classes et Compatibilite
Maitrisez la serialisation Java : comprenez les changements compatibles et incompatibles, evitez InvalidClassException, et gerez efficacement serialVersionUID pour des applications robustes.
Encapsulation en Java et modification de classes avec Java Agents
Decouvrez l'encapsulation en Java avec un exemple pratique, puis apprenez a modifier des classes dynamiquement avec les Java Agents et les varargs.