Skip to content

Restitution TD Machine 2 : Héritage, Polymorphisme et Abstraction en Java


Révisions : Constructeurs et Entrées

Le Constructeur et this

Rôle du Constructeur

Forcer un état initial cohérent au moment de l'allocation mémoire (new). Il porte exactement le nom de la classe et n'a pas de type de retour.

Le mot-clé this lève l'ambiguïté entre les attributs de l'objet courant et les paramètres de la méthode :

public class Robot {
    private String nom;
    private int batterie;

    public Robot(String nom) {
        this.nom = nom; // 'this.nom' est l'attribut
        this.batterie = 100; // Valeur par défaut forcée
    }
}

Les entrées utilisateur avec Scanner

Pour rendre le programme interactif, on lit les entrées console avec Scanner.

import java.util.Scanner; // Import obligatoire

public class Main {
    public static void main(String[] args) {
        Scanner clavier = new Scanner(System.in);

        System.out.print("Entrez le nom du robot : ");
        String nomSaisi = clavier.nextLine();

        Robot r1 = new Robot(nomSaisi);
        clavier.close(); // Bonne pratique : libérer la ressource
    }
}

L'Héritage : Étendre les capacités

Le concept d'Héritage (extends)

L'héritage permet de créer une nouvelle classe (Enfant) basée sur une classe existante (Parent) pour spécialiser son comportement.

  • Mot-clé : extends
  • Relation hiérarchique : Un Drone EST UN Robot. Un Rover EST UN Robot.
  • Bénéfice : L'enfant récupère automatiquement toutes les caractéristiques de son parent (pas besoin de réécrire le code commun).

Visibilité : Le compromis protected

Le problème du private

Les attributs private du parent sont inaccessibles par l'enfant. L'enfant ne peut pas les lire ni les modifier directement !

La solution : protected

Agit comme private vis-à-vis de l'extérieur, mais autorise l'accès direct aux classes enfants. C'est la visibilité idéale pour l'héritage.

public class Robot {
    protected int batterie; // Accessible dans Drone et Rover
    protected int x, y;
}

L'initialisation de l'Enfant : super()

Un enfant ne peut pas exister sans que sa part "parente" ne soit initialisée.

Règle stricte

Le constructeur de la classe enfant doit obligatoirement appeler le constructeur de la classe parente sur sa toute première ligne avec super().

public class Drone extends Robot {
    protected int altitude;

    public Drone(String nom) {
        super(nom); // Appelle le constructeur de Robot(String nom)
        this.altitude = 0; // Initialise l'attribut spécifique au Drone
    }
}

Le mot-clé super : Méthodes et Attributs

Au-delà des constructeurs, le mot-clé super sert de référence directe à l'instance de la classe parente.

Cas d'usage

  • Méthodes : Appeler le comportement d'origine du parent lors d'une redéfinition (pour l'étendre au lieu de l'écraser).
  • Attributs : Accéder explicitement à un attribut protected du parent (particulièrement utile si la classe enfant possède un attribut du même nom qui le "masque").
public class Rover extends Robot {
    @Override
    public void deplacer(int dx, int dy) {
        // 1. Appel de la méthode du parent
        super.deplacer(dx, dy); 

        // 2. Accès explicite à l'attribut du parent
        if (super.batterie < 10) {
            System.out.println("Attention : batterie faible !");
        }
    }
}

Surcharge vs Redéfinition

La Redéfinition (Overriding)

Concept : Réécrire dans une classe enfant une méthode héritée du parent afin d'en modifier ou spécialiser le comportement.

public class Rover extends Robot {
    protected int distanceParcourue;

    @Override // Recommandé : sécurise le code !
    public void deplacer(int dx, int dy) {
        super.deplacer(dx, dy); // Fait le déplacement standard du Robot
        this.distanceParcourue++; // Ajoute l'action spécifique au Rover
    }
}

Note : Si on oublie @Override, le code compile quand même. Mais si on fait une faute de frappe, Java créera une nouvelle méthode sans nous alerter !

La Surcharge (Overloading)

Concept : Créer plusieurs méthodes (ou constructeurs) avec le même nom mais des paramètres différents au sein de la même classe.

public class Robot {
    // Méthode 1 : déplacement avec coût fixe
    public void deplacer(int dx, int dy) {
        this.deplacer(dx, dy, 1); // Appel de la méthode surchargée (DRY)
    }

    // Méthode 2 : déplacement avec coût variable
    public void deplacer(int dx, int dy, int coutEnergie) {
        this.x += dx;
        this.y += dy;
        this.batterie -= coutEnergie;
    }
}

Le piège de la Surcharge

Ce qu'il ne faut pas faire

Pour surcharger une méthode, il faut obligatoirement changer la liste des paramètres (nombre, type ou ordre). Changer uniquement le type de retour provoque une erreur de compilation !

// Dans la même classe :
public int calculerEnergie() { return 10; }

// ERREUR DE COMPILATION !
// Le compilateur ne saura pas laquelle appeler.
public double calculerEnergie() { return 10.0; } 

Polymorphisme et Object

La classe originelle Object

En Java, toutes les classes héritent implicitement de la classe mère Object. Elle fournit un comportement par défaut, notamment la méthode toString().

Par défaut, passer un objet à System.out.println(monRobot) affiche son type et sa référence mémoire (ex: Robot@7a81197d).

Bonne pratique

Toujours redéfinir toString() pour afficher un état lisible.

@Override
public String toString() {
    return "ROBOT " + nom + " | Bat: " + batterie + "%";
}

La magie du Polymorphisme

Concept clé : Une référence de type Parent peut pointer vers un objet de type Enfant. Cela permet de manipuler de façon générique des objets très différents !

Robot[] flotte = new Robot[3]; 

flotte[0] = new Robot("R2D2");
flotte[1] = new Drone("Mavic");    // Un Drone EST un Robot
flotte[2] = new Rover("Curiosity"); // Un Rover EST un Robot

for(int i = 0; i < flotte.length; i++) {
    // La machine virtuelle exécutera le toString() spécifique
    // au véritable type de l'objet (Drone, Rover ou Robot) !
    System.out.println(flotte[i]); 
}

Outils Terminal : Classpath et Flux

Intégrer des bibliothèques : Le Classpath

Les archives .jar contiennent des classes pré-compilées par d'autres développeurs (ex: notre interface graphique FenetreRobot).

L'option -cp (ou -classpath) indique au compilateur et à la JVM où trouver ces fichiers externes :

Commandes dans le terminal

Compilation : javac -cp ".;FenetreRobot.jar" *.java
Exécution : java -cp ".;FenetreRobot.jar" Main
(Le séparateur est un point-virgule sous Windows, et deux-points sous Mac/Linux).

Gestion des erreurs : System.err

Jusqu'ici, nous utilisions System.out.println() pour le flux de sortie standard. La classe System propose un flux séparé pour les alertes et erreurs : System.err.

Avantages :

  • Souvent mis en évidence en rouge dans la console ou l'IDE.
  • Permet de rediriger (dans le terminal) les logs normaux vers un fichier, et les erreurs vers un autre.
if (batterie <= 0) {
    System.err.println("ERREUR : Plus de batterie !");
} else {
    this.x += dx; // Le robot bouge
}

L'Abstraction et les Interfaces

La contrainte architecturale de Java

Règle d'or : Pas d'héritage multiple de classes !

En Java, une classe ne peut utiliser extends que sur UNE SEULE autre classe. Cela évite les ambiguïtés si deux parents définissent la même méthode.

Mais comment faire si une StationMeteo est à la fois une Machine ET un objet Connectable au réseau ?

→ On utilise les Interfaces.

Classes Abstraites vs Interfaces

Classe Abstraite (abstract class)

  • Classe "incomplète" servant de base pour l'héritage (extends).
  • Peut contenir des attributs d'état et du code concret.
  • Impossible de l'instancier directement (new Machine() interdit).

Interface (interface)

  • Un contrat pur sans aucun attribut d'état.
  • Permet de contourner l'interdiction de l'héritage multiple : une classe peut implémenter (implements) une multitude d'interfaces !

Application : Le code contractuel

// L'interface définit le contrat (méthodes obligatoires)
public interface Connectable {
    void seConnecterReseau();
}

// La classe respecte le contrat et fournit le code
public class StationMeteo extends Machine implements Connectable {

    public StationMeteo(String num) {
        super(num);
    }

    // Le compilateur exige la présence de cette méthode !
    @Override
    public void seConnecterReseau() {
        System.out.println("Connexion au serveur établie.");
    }
}

Bilan

Bilan du TD

  • Héritage : Mot-clé extends. Permet de factoriser. Utiliser protected pour l'encapsulation et super() pour initialiser.
  • Surcharge vs Redéfinition :
    • Surcharge (Overload) : Même classe, même nom, paramètres différents.
    • Redéfinition (Override) : Classe enfant, comportement spécialisé.
  • Polymorphisme : Manipuler un ensemble de Drone et Rover via un simple tableau de Robot. La méthode toString() est appelée dynamiquement.
  • Abstraction : Si on veut partager un comportement entre des classes non liées hiérarchiquement, on utilise des interfaces.

Page rédigée à l'aide de Gemini (pas par Gemini)