TD / TP Java 2 : Héritage, Polymorphisme et Abstraction¶
Objectifs et Prérequis
Prérequis : Avoir assimilé les concepts du TD 1 (Classe, Objet, Encapsulation, static, Types primitifs vs Objets).
Objectifs de la séance (3h) :
- Révisions : Consolider l'utilisation du mot-clé
this, des constructeurs, de la méthodemain()et des entrées utilisateur (Scanner). - Nouveautés : Découvrir la puissance de la POO à travers l'Héritage, le Polymorphisme, les Classes abstraites et les Interfaces.
- Outils : Commencer sur BlueJ, puis basculer dans le terminal pour utiliser une bibliothèque graphique externe multi-robots (
.jar).
Étape 1 : Révisions - Constructeurs, this et Scanner (Sous BlueJ)¶
📖 Notion de cours : Constructeur et le mot-clé this
Au TD précédent, nos objets naissaient "vides". Le Constructeur permet de forcer un état initial au moment du new. Il porte exactement le nom de la classe. On utilise this pour désigner "l'attribut de l'objet courant" afin d'éviter la confusion avec les paramètres.
💻 Exercice 1 : Un Robot interactif et mobile¶
- Lancez BlueJ et créez un nouveau projet
FlotteRobotique. - Créez une classe
Robot. Ajoutez-lui ces attributs privés :String nom,int batterie(pourcentage),int xetint y(coordonnées). - Le Constructeur : Ajoutez le code suivant pour forcer l'initialisation :
- Ajoutez les Getters classiques pour tous ces attributs (
getNom(),getBatterie(),getX(),getY()). - Ajoutez une méthode
public void deplacer(int dx, int dy)qui modifiexetyen y ajoutantdxetdy, et qui diminue la batterie de 1. - Ajoutez une méthode
public void afficherStatut()qui affiche dans la console le nom, la batterie et la position du robot.
📖 Notion de cours : Entrées / Sorties avec Scanner
Pour lire les entrées de l'utilisateur, on utilise la classe Scanner. Il faut l'importer avec import java.util.Scanner; tout en haut du fichier. Prenez le réflexe d'ouvrir la documentation officielle pour découvrir toutes les méthodes qu'elle propose !
- Créez une nouvelle classe
Maincontenant la méthode point d'entrée du programme :import java.util.Scanner; public class Main { public static void main(String[] args) { Scanner clavier = new Scanner(System.in); System.out.print("Entrez le nom de votre nouveau robot : "); String nomSaisi = clavier.nextLine(); clavier.close(); // TODO : Instanciez un nouveau Robot en utilisant 'nomSaisi' // TODO : Appelez la méthode afficherStatut() de votre robot } } - Testez : Dans BlueJ, compilez, faites un clic droit sur la classe
Mainet lancezvoid main(String[] args).
Étape 2 : L'Héritage - Étendre les capacités¶
📖 Notion de cours : L'Héritage et protected
L'Héritage permet de créer une nouvelle classe basée sur une classe existante avec le mot-clé extends.
Problème : Les attributs private du parent sont inaccessibles, même pour l'enfant !
Solution : Le modificateur protected. Il agit comme private pour l'extérieur, mais autorise l'accès aux classes enfants.
💻 Exercice 2 : La naissance du Drone¶
- Dans la classe
Robot, changez la visibilité de TOUS vos attributs : remplacezprivateparprotected. - Créez une classe
Dronehéritant deRobot. Ajoutez-lui un attribut propre :protected int altitude; - Le constructeur de l'enfant : Un enfant doit obligatoirement appeler le constructeur de son parent. On utilise
super()sur la première ligne : - Ajoutez une méthode
public void decoller()qui augmente l'altitude de 10 et diminue la batterie de 5.
📖 Notion de cours : La Redéfinition et l'annotation @Override
La redéfinition (overriding) consiste, pour une classe enfant, à réécrire une méthode héritée de son parent pour en modifier ou en spécialiser le comportement.
L'annotation @Override s'écrit juste au-dessus de la méthode redéfinie. Elle n'est pas techniquement obligatoire, mais elle est fortement recommandée car :
- Elle sécurise le code : Le compilateur vérifiera que la méthode existe bien chez le parent. Si vous faites une faute de frappe (ex:
bougerau lieu dedeplacer) ou une erreur dans les paramètres, il bloquera la compilation au lieu de créer silencieusement une nouvelle méthode. - Elle améliore la lisibilité : Elle indique clairement aux autres développeurs que vous êtes en train d'altérer un comportement standard.
💻 Exercice 3 : À vous de jouer ! Le Rover terrestre¶
Créez un nouveau type de robot sans aide !
- Créez une classe
Roverqui hérite deRobot. - Ajoutez-lui un attribut privé :
int distanceParcourue. - Écrivez son constructeur (pensez à
super()). - Redéfinissez (
@Override) sa méthodedeplacer(int dx, int dy): elle doit appelersuper.deplacer(dx, dy)afin de bouger réellement (et consommer de la batterie), mais elle doit en plus incrémenterdistanceParcourue!
Étape 3 : Polymorphisme et la méthode toString()¶
📖 Notion de cours : Object et la méthode toString()
En Java, toutes les classes héritent implicitement d'une classe originelle : Object. N'hésitez pas à consulter la documentation officielle de Object pour voir les méthodes qu'elle offre.
L'une d'elles est public String toString(). Java l'appelle automatiquement lorsque vous faites System.out.println(monObjet);. L'idée est de la redéfinir (@Override) dans nos classes pour afficher des informations utiles.
💻 Exercice 4 : L'art du déguisement¶
- Dans
Robot, remplacezafficherStatut()par la redéfinition detoString(): - Dans
Drone, redéfinissez aussitoString()pour inclure l'altitude, en réutilisant le code du parent viasuper.toString(). - (Optionnel) Faites de même dans
Roverpour inclure la distance parcourue. - Retournez dans votre classe
Main. Effacez le contenu dumain()précédent. Nous allons créer un tableau contenant différents types d'objets :public class Main { public static void main(String[] args) { // Un tableau de 3 références "Robot" Robot[] flotte = new Robot[3]; flotte[0] = new Robot("R2D2"); flotte[1] = new Drone("Mavic"); // Polymorphisme ! Un Drone EST un Robot. flotte[2] = new Rover("Curiosity"); for(int i = 0; i < flotte.length; i++) { // Observez la magie du polymorphisme couplée à toString() : System.out.println(flotte[i]); } } }
Étape 4 : Le Poste de Commande Multi-Robots (Terminal)¶
Fermez BlueJ. Nous passons dans le terminal pour compiler nos fichiers manuellement et intégrer une bibliothèque externe graphique.
💻 Exercice 5 : Mise en peinture (Refactoring)¶
Avant de lancer l'interface graphique, nos robots ont besoin d'une couche de peinture !
Dans l'exercice suivant, nous allons vous fournir une interface FenetreRobot qui s'attend en effet à pouvoir lire la couleur de chaque robot pour les dessiner à l'écran.
C'est à vous de modifier l'architecture de votre code pour intégrer cette nouveauté :
- Dans
Robot.java, importez la classejava.awt.Colortout en haut du fichier. Consultez sa documentation pour voir comment utiliser les couleurs pré-définies (les constantes commeColor.RED,Color.BLUE, etc.). - Ajoutez un attribut
couleurde typeColorà votre classeRobot. Réfléchissez à sa visibilité :public,protectedouprivate? - Modifiez le constructeur de
Robotpour qu'il prenne un deuxième paramètre de typeColoret initialise l'attribut. - Ajoutez le getter correspondant :
public Color getCouleur(). - Attention : Si vous recompilez maintenant, vous aurez des erreurs dans
DroneetRover! C'est normal : le constructeur parent a changé. Modifiez les constructeurs deDroneetRoverpour qu'ils acceptent aussi une couleur en paramètre, et passez-la correctement au parent viasuper(..., ...);.
📖 Notion de cours : La Surcharge (Overloading)
La surcharge consiste à définir dans une même classe plusieurs méthodes (ou constructeurs) qui portent le même nom, mais qui diffèrent par leurs paramètres (nombre, type ou ordre).
- L'objectif : Offrir de la souplesse à l'utilisateur de votre classe en proposant plusieurs façons de faire la même action. Par exemple,
System.out.println()est surchargée : on peut lui donner unint, uneStringou unObjet. - La règle : Le compilateur sait quelle méthode exécuter en analysant les arguments que vous lui passez. Attention : changer uniquement le type de retour d'une méthode ne suffit pas à la surcharger !
💻 Exercice 6 : La Surcharge à la rescousse¶
Dans l'exercice précédent, nous avons forcé tous les robots à prendre une couleur à la création. Mais que se passe-t-il si un utilisateur veut créer un robot sans se soucier de sa couleur ? Nous allons surcharger le constructeur et la méthode de déplacement !
- Surcharge du Constructeur : Dans la classe
Robot, ajoutez un deuxième constructeur qui ne prend que lenomen paramètre. À l'intérieur, plutôt que de réécrire toute l'initialisation, appelez l'autre constructeur en lui passant une couleur par défaut (par exemple gris) en utilisant la syntaxethis(...)sur la première ligne : - Surcharge de méthode : Actuellement,
deplacer(int dx, int dy)consomme toujours 1 point de batterie. Créez une nouvelle méthodepublic void deplacer(int dx, int dy, int coutEnergie)dans la classeRobot. - Copiez la logique de déplacement dans cette nouvelle méthode pour qu'elle déduise
coutEnergiede la batterie au lieu de 1. - Optimisation pro : Modifiez votre ancienne méthode
deplacer(int dx, int dy)pour qu'elle se contente d'appeler la nouvelle :this.deplacer(dx, dy, 1);. C'est le principe du code "DRY" (Don't Repeat Yourself). - Testez : Dans le
Main, instanciez un robot sans préciser de couleur, et faites-le se déplacer en utilisant la méthode à 3 paramètres (par exemple un sprint qui coûte 10 d'énergie).
💻 Exercice 7 : Interface Graphique¶
Téléchargez le fichier FenetreRobot.jar. Il contient une interface graphique capable d'afficher votre flotte de robots. Elle dessinera un cercle pour les Robot simples, un triangle pour les Drone, et un carré pour les Rover, le tout dans la couleur de l'objet !
- Naviguez dans votre terminal vers le dossier de votre projet. Placez le fichier
FenetreRobot.jardans ce dossier. - Modifiez votre fichier
Main.javapour tester l'interface. Instanciez plusieurs robots avec des couleurs (observez l'import deColor) dans un tableau passez ce tableau à l'interface graphique :import java.awt.Color; public class Main { public static void main(String[] args) { // On crée un tableau capable de contenir 3 références de Robots Robot[] maFlotte = new Robot[3]; // Les constructeurs exigent maintenant une couleur ! maFlotte[0] = new Robot("Standard-1", Color.BLACK); maFlotte[1] = new Drone("SkyWatcher", Color.RED); maFlotte[2] = new Rover("Wall-E", new Color(255, 165, 0)); // Code RGB pour le orange // On lance l'interface graphique contenue dans le .jar en lui confiant notre tableau // (Attention : le compilateur BlueJ ne connaîtra pas cette classe, il faut compiler dans le terminal). FenetreRobot posteDeCommande = new FenetreRobot(maFlotte); } }
📖 Notion de cours : Le Classpath (-cp) et les archives .jar
Une archive .jar (Java ARchive) est un fichier regroupant des classes pré-compilées fournies par d'autres développeurs. Pour utiliser une bibliothèque externe, il faut indiquer au compilateur (javac) et à la machine virtuelle (java) où la trouver via l'option -cp (Classpath).
- Compilation : Compilez votre programme en incluant le
.jardans le classpath.- Windows :
javac -cp ".;FenetreRobot.jar" *.java - Mac/Linux :
javac -cp ".:FenetreRobot.jar" *.java
- Windows :
- Exécution : Lancez la machine virtuelle.
- Windows :
java -cp ".;FenetreRobot.jar" Main - Mac/Linux :
java -cp ".:FenetreRobot.jar" Main
- Windows :
-
Amusez-vous à déplacer vos robots. Utilisez le sélecteur en bas à gauche pour choisir le robot à contrôler.
-
Le bug de l'énergie infinie : Continuez de déplacer un robot jusqu'à ce que sa batterie atteigne
0%. Que se passe-t-il si vous cliquez encore ? Le robot continue de bouger et sa batterie devient négative ! C'est inadmissible pour une simulation réaliste.
📖 Notion de cours : Les flux de sortie (System.out vs System.err)
Jusqu'à présent, vous avez utilisé System.out.println() pour le flux de sortie standard. La classe System fournit un flux spécifiquement dédié aux alertes et erreurs : System.err.println(). La plupart des consoles afficheront ce texte en rouge pour alerter le développeur !
- Correction : Retournez dans le code de
Robot(etRover). Modifiez la méthodedeplacer(...)avec unif. Le robot ne doit bouger et consommer de l'énergie que si sa batterie est strictement positive. Sinon, affichez un message d'erreur rouge avecSystem.err.println(...). - Cloué au sol : Appliquez exactement la même logique à votre classe
Drone. Modifiez sa méthodedecoller()pour qu'elle vérifie l'état de la batterie avant d'autoriser le décollage, et affiche une erreur en rouge dans le cas contraire. - Recompilez vos classes et testez à nouveau avec l'interface graphique. Observez la console lorsque la batterie de vos robots (et de votre drone) tombe à zéro !
Étape 5 : L'Abstraction (Toujours dans le Terminal)¶
📖 Notion de cours : Classes Abstraites, Interfaces et l'Interdiction de l'Héritage Multiple
- Classe Abstraite (
abstract class) : Classe "incomplète" contenant des méthodes abstraites (sans code). On ne peut pas faire denewdessus. Elle sert de base pour l'héritage. - Interface (
interface) : Contrat pur sans aucun attribut d'état ni code implémenté.
⚠️ RÈGLE D'OR : En Java, l'héritage multiple est interdit ! On ne peut faire extends que d'une seule classe mère. Les Interfaces permettent d'outrepasser cette limite car une classe peut faire implements de plusieurs interfaces simultanément !
💻 Exercice 7 : Les Contrats Logiciels et le Compilateur¶
-
Restez dans votre terminal. Créez un fichier
Connectable.java: -
Créez un fichier
Machine.java: -
Confrontation avec le compilateur : Créez
StationMeteo.javaqui hérite de l'un et implémente l'autre, mais ne lui donnez que son constructeur pour le moment : - Dans le terminal, tentez de compiler :
javac StationMeteo.java. Lisez attentivement le message d'erreur. Que vous réclame le compilateur ? - Corrigez
StationMeteo.javaen ajoutant le code (même basique avec desSystem.out.println) des trois méthodes obligatoires manquantes (demarrer(),seConnecterReseau()etseDeconnecter()). Compilez de nouveau jusqu'à ce que l'erreur disparaisse. - À vous de jouer : Sans modèle cette fois, créez un fichier
CapteurThermique.java. Un capteur thermique est uneMachine, mais il n'est pasConnectable. Écrivez sa classe, son constructeur, et implémentez l'unique méthode obligatoire. Compilez pour vérifier.
💻 Exercice 8 : Mise en orbite¶
Créez enfin un MainSysteme.java pour tester l'ensemble :
public class MainSysteme {
public static void main(String[] args) {
StationMeteo sm = new StationMeteo("SM-Alpha");
sm.demarrer();
// Polymorphisme avec les Interfaces !
// Je peux stocker ma station dans une référence "Connectable" car elle respecte le contrat
Connectable appareilReseau = sm;
appareilReseau.seConnecterReseau();
// TODO : Instanciez un CapteurThermique
// TODO : Appelez sa méthode pour le démarrer
}
}
Compilez tout avec javac *.java (ici pas besoin de -cp car ce programme n'utilise pas l'interface graphique), puis exécutez avec java MainSysteme.
📝 Auto-évaluation Interactive¶
Cochez la bonne réponse pour vérifier vos acquis.
1. À quoi sert le mot-clé super() dans le constructeur d'une classe enfant ?
2. Quelle est la principale différence entre une Interface et une Classe Abstraite en Java ?
3. Soit le code : Machine m = new StationMeteo();. Comment appelle-t-on ce principe ?
4. Pourquoi utilise-t-on la visibilité protected sur les attributs d'une classe parent ?
5. Que se passe-t-il si vous passez un objet directement à System.out.println() sans avoir redéfini la méthode toString() ?
6. Dans le terminal, à quoi sert l'option -cp (ou -classpath) lors de la compilation ?
7. Quel est l'intérêt d'utiliser System.err.println() plutôt que System.out.println() pour les erreurs ?
8. En Java, qu'est-ce que la redéfinition (overriding) d'une méthode ?
9. Que se passe-t-il si vous redéfinissez correctement une méthode du parent dans la classe enfant, mais que vous oubliez d'écrire l'annotation @Override au-dessus ?
10. Surcharge ou Redéfinition ? Observez ces deux méthodes :
Dans la classe parente Robot : public void recharger()
Dans la classe enfant Drone extends Robot : public void recharger()
11. Surcharge ou Redéfinition ? Observez ces deux méthodes au sein de la MÊME classe Robot :
public void deplacer(int distance)
public void deplacer(int dx, int dy)
12. Le piège classique : Observez ces deux méthodes au sein de la MÊME classe :
public int calculerEnergie()
public double calculerEnergie()
Sujet rédigé à l'aide de Gemini (pas par Gemini)