Redéfinition de méthodes (Run-Time)
Fait partie de la section Programmation Orientée Objet du Journey Java de Coddy — leçon 29 sur 87.
Alors que la surcharge de méthodes est résolue au moment de la compilation, la redéfinition de méthode démontre le polymorphisme au moment de l'exécution. Ici, Java décide quelle méthode exécuter lorsque le programme est en cours d'exécution, en se basant sur le type réel de l'objet plutôt que sur le type déclaré de la variable.
Vous avez découvert la redéfinition de méthode (method overriding) dans le chapitre sur l'héritage. Ce qui la rend polymorphe, c'est la manière dont Java la gère lorsqu'une référence parente pointe vers un objet enfant :
public class Animal {
public void speak() {
System.out.println("Some sound");
}
}
public class Dog extends Animal {
@Override
public void speak() {
System.out.println("Bark!");
}
}
public class Cat extends Animal {
@Override
public void speak() {
System.out.println("Meow!");
}
}Observez maintenant ce qui se passe lorsque nous utilisons des références de type parent :
Animal myPet = new Dog();
myPet.speak(); // Résultat : Bark!
myPet = new Cat();
myPet.speak(); // Résultat : Meow!Même si myPet est déclaré comme Animal, Java appelle la méthode redéfinie en fonction du type réel de l'objet au moment de l'exécution. C'est ce qu'on appelle le dispatching dynamique de méthodes. La JVM regarde ce qu'est réellement l'objet, et non ce que la variable indique.
Cela permet des modèles puissants comme le traitement uniforme de différents objets :
Animal[] pets = {new Dog(), new Cat(), new Dog()};
for (Animal pet : pets) {
pet.speak(); // Chacun appelle sa propre version
}Le polymorphisme à l'exécution vous permet d'écrire du code flexible qui fonctionne avec des types parents tout en utilisant automatiquement le comportement enfant approprié.
Défi
FacileConstruisons un système de notification qui démontre le polymorphisme au moment de l'exécution. Vous allez créer une hiérarchie de types de notification où le même appel de méthode produit des sorties différentes selon le type d'objet réel au moment de l'exécution.
Vous organiserez votre code sur quatre fichiers :
Notification.java: Créez la classe de base que toutes les notifications partagent. Chaque notification possède un champrecipient(String). Incluez un constructeur pour l'initialiser, une méthode getter, et une méthodesend()qui affiche :Sending notification to [recipient]EmailNotification.java: Créez une classe qui étend Notification. Les notifications par e-mail ajoutent un champsubject. Utilisezsuperpour l'initialisation du parent. Surchargez la méthodesend()pour afficher :Emailing [recipient]: [subject]SMSNotification.java: Créez une autre classe qui étend Notification. Les notifications SMS ajoutent un champphoneNumber. Surchargezsend()pour afficher :Texting [phoneNumber] for [recipient]Main.java: C'est ici que le polymorphisme au moment de l'exécution brille ! Vous recevrez trois entrées : un nom de destinataire, un sujet d'e-mail et un numéro de téléphone. Créez un tableau de référencesNotificationcontenant trois objets dans cet ordre : une Notification de base, une EmailNotification et une SMSNotification (utilisant toutes le même destinataire). Ensuite, parcourez le tableau et appelezsend()sur chaque élément. Regardez comment Java appelle automatiquement la version surchargée correcte en fonction de ce qu'est réellement chaque objet !
Vous recevrez trois entrées : le nom du destinataire (String), le sujet de l'e-mail (String) et le numéro de téléphone (String).
Votre sortie devrait afficher trois lignes - chaque appel à send() produit une sortie différente même si vous appelez la même méthode sur des références Notification. C'est la répartition dynamique des méthodes à l'œuvre !
Aide-mémoire
La redéfinition de méthode démontre le polymorphisme au moment de l'exécution, où Java décide quelle méthode exécuter pendant que le programme tourne, en se basant sur le type réel de l'objet plutôt que sur le type déclaré de la variable.
Lorsqu'une référence parente pointe vers un objet enfant, Java appelle la méthode redéfinie en fonction du type d'objet réel au moment de l'exécution :
public class Animal {
public void speak() {
System.out.println("Some sound");
}
}
public class Dog extends Animal {
@Override
public void speak() {
System.out.println("Bark!");
}
}
Animal myPet = new Dog();
myPet.speak(); // Output: Bark!
myPet = new Cat();
myPet.speak(); // Output: Meow!C'est ce qu'on appelle la répartition dynamique des méthodes. La JVM regarde ce qu'est réellement l'objet, et non ce que dit la variable.
Le polymorphisme au moment de l'exécution permet de traiter différents objets de manière uniforme :
Animal[] pets = {new Dog(), new Cat(), new Dog()};
for (Animal pet : pets) {
pet.speak(); // Chacun appelle sa propre version
}Essayez vous-même
import java.util.Scanner;
class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// Lire les entrées
String recipient = scanner.nextLine();
String subject = scanner.nextLine();
String phoneNumber = scanner.nextLine();
// TODO: Créer un tableau de références Notification avec 3 éléments
// Le tableau doit contenir (dans cet ordre) :
// 1. Un objet Notification de base
// 2. Un objet EmailNotification
// 3. Un objet SMSNotification
// Tous utilisant le même recipient
// TODO: Parcourir le tableau et appeler send() sur chaque élément
// Cela démontre le polymorphisme au moment de l'exécution !
}
}
Cette leçon comprend un petit quiz. Commencez la leçon pour y répondre et suivre votre progression.
Toutes les leçons de Programmation Orientée Objet
1Fondamentaux de la POO
Fichiers externesIntroduction à la POOClasses vs ObjetsLe mot-clé thisMéthodesChamps (Attributs)Méthode constructeurSurcharge de constructeurRécapitulatif - Calculatrice simple4Héritage
Héritage de base (extends)Le mot-clé superRedéfinition de méthode (@Override)Chaînage de constructeursLa classe ObjectHéritage simple et multiniveauPourquoi pas d'héritage multiple de classesRécapitulatif - Hiérarchie des employés7Méthodes spéciales et classe Object
Méthode toString()equals() et hashCode()Méthode clone()compareTo() et ComparableInterface ComparatorRécapitulatif - Tri personnalisé2Modificateurs d'accès et Encapsulation
Aperçu des niveaux d'accèsMéthodes Getter et SetterMasquage d'informationsLe mot-clé finalRécapitulatif - Gestionnaire de compte bancaire5Polymorphisme
Bases de la surcharge de méthodesRedéfinition de méthodes (Run-Time)Upcasting et DowncastingL'opérateur instanceofClasses et méthodes abstraitesRécapitulatif - Calculateur de formes8Concepts avancés de la POO
Composition vs HéritageAgrégation vs CompositionClasses internes, imbriquées et anonymesEnums et méthodes d'EnumRecords (Java 16+)Classes scellées (Java 17+)11Patrons de conception, partie 1
Introduction aux patrons de conceptionPatron SingletonPatron FabriquePatron MonteurPatron ObservateurPatron Stratégie3Propriétés de classe et membres statiques
Variables d'instance vs variables statiquesMéthodes statiquesBlocs statiquesConstantes (static final)Récapitulatif - Compteur et utilitaire6Interfaces et Classes Abstraites
Introduction aux InterfacesImplémentation d'InterfacesImplémentation d'Interfaces MultiplesDefault et Static dans les InterfacesClasses Abstraites vs InterfacesInterfaces FonctionnellesRécapitulatif - Système de Paiement9La généricité
Introduction à la généricitéClasses génériquesMéthodes génériquesParamètres de type bornésWildcards (?, extends, super)Récapitulatif - Conteneur générique12Patrons de conception, partie 2
Patron CommandePatron AdaptateurPatron DécorateurPatron Template MethodPatron ÉtatPatron CompositePatron Itérateur