Overloading vs Overriding en Java : Guide Complet du Polymorphisme

Maitrisez les differences entre method overloading et method overriding en Java. Decouvrez les bonnes pratiques, les pieges a eviter et des exemples concrets pour ecrire du code polymorphe efficace.

Mahmoud DEVO
Mahmoud DEVO
December 27, 2025 7 min read
Overloading vs Overriding en Java : Guide Complet du Polymorphisme

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 :

  1. Concevoir des APIs intuitives - L’overloading permet de proposer plusieurs signatures pour une meme operation
  2. Implementer l’heritage correctement - L’overriding permet de specialiser le comportement des sous-classes
  3. Eviter les bugs subtils - Une mauvaise comprehension peut mener a des comportements inattendus
  4. 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

CritereOverloadingOverriding
Moment de resolutionCompilation (statique)Execution (dynamique)
SignatureDifferenteIdentique
Heritage requisNonOui
AnnotationAucune@Override recommandee
Type de retourPeut varierIdentique ou covariant
Modificateur d’accesPeut varierMeme ou plus permissif
Methodes statiquesOuiNon (hiding)
Methodes finalesOuiNon
Methodes priveesOuiNon
PerformancesPlus 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

  1. 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!
  1. 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);
}
  1. 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

  1. Toujours utiliser @Override
@Override  // Le compilateur verifiera que vous redefinissez bien une methode
public String toString() {
    return "MyClass{...}";
}
  1. 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
    }
}
  1. 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 :

  1. Utilisez l’overloading pour offrir des variantes d’une meme operation
  2. Utilisez l’overriding pour implementer le polymorphisme d’heritage
  3. Annotez toujours avec @Override pour detecter les erreurs au compile-time
  4. Soyez vigilant sur la resolution statique vs dynamique
  5. 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
Advertisement

In-Article Ad

Dev Mode

Share this article

Mahmoud DEVO

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