6.2Polymorphisme
6.2.1 – Redéfinition (overriding)
La redéfinition (ou overriding) permet à une classe fille de fournir une implémentation spécifique d'une méthode qui existe déjà dans la classe mère. La méthode redéfinie doit avoir la même signature (nom, paramètres, type de retour) que la méthode de la classe mère.
🔑 Principe de la redéfinition
Quand une classe fille redéfinit une méthode de la classe mère, la version de la classe fille est utilisée lorsque la méthode est appelée sur un objet de la classe fille.
Exemple : Redéfinition simple
public class Personne {
public void sePresenter() {
System.out.println("Je suis une personne");
}
public void travailler() {
System.out.println("Je travaille");
}
}
public class Etudiant extends Personne {
@Override
public void sePresenter() {
System.out.println("Je suis un étudiant");
}
// travailler() n'est pas redéfinie, donc utilise la version de Personne
}
// Utilisation
Personne p = new Personne();
p.sePresenter(); // Affiche : Je suis une personne
Etudiant e = new Etudiant();
e.sePresenter(); // Affiche : Je suis un étudiant (version redéfinie)
e.travailler(); // Affiche : Je travaille (version héritée)
📝 Annotation @Override
L'annotation @Override indique explicitement que vous redéfinissez une méthode. Elle est optionnelle mais fortement recommandée car elle :
- Améliore la lisibilité du code
- Permet au compilateur de détecter les erreurs (si la signature ne correspond pas)
- Documente l'intention de redéfinir une méthode
Exemple : Avec et sans @Override
public class Etudiant extends Personne {
// Sans @Override (fonctionne mais moins clair)
public void sePresenter() {
System.out.println("Je suis un étudiant");
}
// Avec @Override (recommandé)
@Override
public void sePresenter() {
System.out.println("Je suis un étudiant");
}
}
⚖️ Règles de redéfinition
Pour qu'une méthode soit correctement redéfinie, elle doit respecter ces règles :
- Même signature : Nom, paramètres et type de retour identiques
- Visibilité : La méthode redéfinie ne peut pas être moins visible que celle de la classe mère
- Exceptions : La méthode redéfinie ne peut pas lever plus d'exceptions que celle de la classe mère
- final : Une méthode
finalne peut pas être redéfinie
Exemple d'erreur :
public class Personne {
public void sePresenter() { }
}
public class Etudiant extends Personne {
// ❌ ERREUR : type de retour différent
// public int sePresenter() { return 0; }
// ❌ ERREUR : paramètres différents (c'est une surcharge, pas une redéfinition)
// public void sePresenter(String message) { }
// âś… CORRECT : mĂŞme signature
@Override
public void sePresenter() { }
}
🔄 Appel de la méthode parente avec super
Dans une méthode redéfinie, vous pouvez appeler la version de la classe mère avec super pour étendre le comportement plutôt que de le remplacer complètement.
Exemple : Extension du comportement
public class Personne {
public void sePresenter() {
System.out.println("Je suis une personne.");
}
}
public class Etudiant extends Personne {
private String numeroEtudiant;
@Override
public void sePresenter() {
super.sePresenter(); // Appelle la méthode de Personne
System.out.println("Mon numéro étudiant est : " + numeroEtudiant);
}
}
// Utilisation
Etudiant e = new Etudiant();
e.numeroEtudiant = "E12345";
e.sePresenter();
// Affiche :
// Je suis une personne.
// Mon numéro étudiant est : E12345
6.2.2 – Surcharge (overloading)
La surcharge (ou overloading) permet de définir plusieurs méthodes avec le même nom mais avec des paramètres différents (nombre, type, ordre). Le compilateur choisit la méthode appropriée en fonction des arguments passés.
🔑 Principe de la surcharge
La surcharge permet d'avoir plusieurs versions d'une méthode pour différents types de données ou nombres de paramètres, tout en gardant un nom cohérent.
Exemple : Surcharge de méthodes
public class Calculatrice {
// Version 1 : deux entiers
public int additionner(int a, int b) {
System.out.println("Addition de deux entiers");
return a + b;
}
// Version 2 : deux décimaux
public double additionner(double a, double b) {
System.out.println("Addition de deux décimaux");
return a + b;
}
// Version 3 : trois entiers
public int additionner(int a, int b, int c) {
System.out.println("Addition de trois entiers");
return a + b + c;
}
// Version 4 : un tableau d'entiers
public int additionner(int[] nombres) {
System.out.println("Addition d'un tableau");
int somme = 0;
for (int n : nombres) {
somme += n;
}
return somme;
}
}
// Utilisation
Calculatrice calc = new Calculatrice();
calc.additionner(5, 3); // Appelle la version 1
calc.additionner(5.5, 3.2); // Appelle la version 2
calc.additionner(1, 2, 3); // Appelle la version 3
calc.additionner(new int[]{1, 2, 3, 4}); // Appelle la version 4
📋 Règles de surcharge
Pour qu'une méthode soit considérée comme une surcharge, elle doit :
- Avoir le même nom que les autres méthodes
- Avoir des paramètres différents (nombre, type, ou ordre)
- Le type de retour peut être différent, mais ce n'est pas suffisant pour créer une surcharge
Exemple : Surcharge valide et invalide
public class Exemple {
// ✅ VALIDE : paramètres différents
public int calculer(int a, int b) { return a + b; }
public double calculer(double a, double b) { return a + b; }
public int calculer(int a, int b, int c) { return a + b + c; }
// ❌ ERREUR : même signature, seul le type de retour diffère
// public int calculer(int a, int b) { return a + b; }
// public double calculer(int a, int b) { return a + b; } // ERREUR !
}
🔄 Surcharge vs Redéfinition
Il est important de distinguer la surcharge (overloading) de la redéfinition (overriding) :
| Caractéristique | Surcharge (Overloading) | Redéfinition (Overriding) |
|---|---|---|
| Où | Même classe | Classe mère et classe fille |
| Signature | Paramètres différents | Même signature |
| Type de retour | Peut être différent | Doit être identique |
| Visibilité | Peut être différente | Ne peut pas être moins visible |
| @Override | Non utilisé | Recommandé |
| Résolution | À la compilation | À l'exécution (polymorphisme) |
đź’ˇ Exemples pratiques de surcharge
Exemple : Constructeurs surchargés
public class Personne {
private String nom;
private int age;
// Constructeur 1 : sans paramètres
public Personne() {
this.nom = "Inconnu";
this.age = 0;
}
// Constructeur 2 : avec nom seulement
public Personne(String nom) {
this.nom = nom;
this.age = 0;
}
// Constructeur 3 : avec nom et âge
public Personne(String nom, int age) {
this.nom = nom;
this.age = age;
}
}
Exemple : Méthodes utilitaires surchargées
public class Utilitaire {
// Afficher un entier
public void afficher(int valeur) {
System.out.println("Entier : " + valeur);
}
// Afficher un décimal
public void afficher(double valeur) {
System.out.println("Décimal : " + valeur);
}
// Afficher une chaîne
public void afficher(String valeur) {
System.out.println("Chaîne : " + valeur);
}
// Afficher un tableau
public void afficher(int[] valeurs) {
System.out.print("Tableau : ");
for (int v : valeurs) {
System.out.print(v + " ");
}
System.out.println();
}
}
💡 Points clés à retenir
- Surcharge : Même nom, paramètres différents (même classe)
- Redéfinition : Même signature, implémentation différente (classe mère/fille)
- Résolution : La surcharge est résolue à la compilation, la redéfinition à l'exécution
- Type de retour : Peut être différent pour la surcharge, doit être identique pour la redéfinition
- @Override : Utilisé pour la redéfinition, pas pour la surcharge
- Utilisez la surcharge pour offrir plusieurs façons d'appeler une méthode avec différents types de données
- La surcharge améliore la lisibilité en gardant un nom cohérent pour des opérations similaires
- Ne confondez pas surcharge et redéfinition : la surcharge est dans la même classe, la redéfinition entre classes parentes et filles
- Les constructeurs sont souvent surchargés pour offrir différentes façons d'initialiser un objet