L'héritage en Java

10 Sep 2019 10 Sep 2019 12515 vues ESSADDOUKI Mostafa 15 min de lecture
Introduction
1 Nouveautés de Java 11 2 Différences entre JDK, JRE et JVM 3 Structure d'un programme Java - Hello World 4 Mots clés et conventions de dénomination en Java 5 Types de données intégrés en Java 6 Les variables en Java 7 Classes enveloppe - Number, Integer, Double ... 8 Lire les entrées clavier en Java
Structures de contrôle
9 Les opérateurs en Java 10 Les structures conditionnelles en Java 11 Les boucles en Java 12 Instructions de contrôle de boucle - break, continue
Chaines de caractères
13 Les chaines en Java - API String 14 Les chaines en Java - StringBuffer et StringBuilder 15 Les expressions régulières en Java
Programmation OO
16 Objets et classes en Java 17 Modificateurs d'accès Java - public, private, protected et package 18 Méthodes et surcharge des méthodes en Java 19 les constructeurs en Java 20 L'héritage en Java 21 Classes abstraites en Java 22 Interfaces et héritage multiple en Java 23 Les classes imbriquées en Java 24 Les singletons en Java 25 Classes et méthodes génériques 26 Interface fonctionnelle et expressions Lambda en Java
Tableaux et collections
27 Les tableaux en Java 28 Classe Arrays - java.util.Arrays 29 Les listes dynamiques - java.util.ArrayList 30 Les listes chaînées en Java - java.util.LinkedList 31 HashSet en Java - java.util.HashSet 32 HashMap en Java - java.util.HashMap
Gestion des fichiers
33 Comprendre les fichiers informatiques 34 Utilisation des classes Path et Files en Java 35 Lecture et écriture dans un fichier en Java 36 Fichiers à accès aléatoire en Java
Gestion d'exceptions
37 Gestion d'exceptions en Java 38 Créez vos propres classes d'exception en Java
Programmation concurrente
39 Introduction à la programmation concurrente en Java - Multi-threads 40 classe java.lang.Thread 41 Synchronisation des threads en Java
Cours Java pour les débutants — Étape 20 sur 41

Héritage en Java

Objectifs

Comprendre le mécanisme de l'héritage en programmation orientée objet, maîtriser la terminologie associée, et savoir implémenter des relations d'héritage en Java.

Prérequis

Notions de classes, d'objets, d'encapsulation et de constructeurs en Java.

Définition — Héritage

L'héritage est un mécanisme fondamental de la programmation orientée objet qui permet à une classe d'acquérir tous les attributs et méthodes d'une autre classe. Vous pouvez créer une nouvelle classe simplement en indiquant en quoi elle diffère d'une classe qui a déjà été développée et testée.

Vous connaissez le concept d'héritage de toutes sortes de situations non programmables. En biologie, votre groupe sanguin et la couleur de vos yeux sont le produit de gènes hérités. De nombreux attributs sont hérités.

De même, les classes que vous créez en Java peuvent hériter des données et des méthodes des classes existantes. Une nouvelle classe contient automatiquement les champs et méthodes de la classe d'origine.

Diagramme d'héritage Personne → Professeur

Héritage : Professeur hérite de Personne

Terminologie de l'héritage

Termes clés
  • Classe de base / Superclasse / Classe mère : classe utilisée comme base d'héritage (ex: Personne)
  • Classe dérivée / Sous-classe / Classe enfant : classe qui hérite d'une classe de base (ex: Professeur)
Relation "est un" (is-a)

Un objet de classe dérivée "est aussi" une instance de la classe de base. Vous pouvez donc affecter la référence d'un objet de classe dérivée à une référence de classe de base. De même, si une méthode accepte une référence d'objet de classe de base, elle acceptera également les références à ses classes dérivées.

Ne pas confondre "est un" et "a un"
  • "est un" (is-a) : décrit l'héritage (ex: "Un professeur est une personne")
  • "a un" (has-a) : décrit la composition ou l'agrégation (ex: "Une entreprise a des départements")
Composition vs Agrégation
  • Composition : relation dans laquelle une classe contient des membres d'une autre classe qui ne continueraient pas d'exister sans l'objet conteneur. (Ex: si une entreprise ferme, ses départements ferment aussi)
  • Agrégation : relation dans laquelle une classe contient des membres d'une autre classe qui continueraient d'exister sans l'objet conteneur. (Ex: si un département ferme, les employés continuent d'exister)

Le mot-clé extends

Vous utilisez le mot-clé extends pour réaliser l'héritage en Java :

Syntaxe de base Java
public class Professeur extends Personne {
    // Ajout de nouveaux attributs et méthodes
}

Chaque Professeur reçoit automatiquement les attributs et méthodes de la superclasse Personne. Vous pouvez ensuite ajouter de nouveaux attributs et méthodes à la sous-classe.

Exemple 1 — Définition d'une sous-classe
Classe Professeur
public class Professeur extends Personne {
    private double salaire;
    private int ppr;
    
    public double getSalaire() {
        return salaire;
    }
    
    public int getPpr() {
        return ppr;
    }
}
Explication
Professeur hérite des attributs
nom et cin de Personne, et ajoute
ses propres attributs (salaire, ppr)
Instanciation d'un objet de sous-classe
Professeur prof = new Professeur();

L'objet prof a accès à toutes les méthodes de la classe Personne ainsi qu'aux méthodes de sa propre classe.

L'héritage est unidirectionnel

Un enfant hérite d'une classe mère, mais l'inverse n'est pas vrai. Lorsque vous instanciez un objet Personne, il n'a pas accès aux méthodes de Professeur. À la création de la classe mère, on ne sait pas quelles sous-classes pourraient exister à l'avenir.

L'opérateur instanceof

L'opérateur instanceof permet de déterminer si un objet est une instance d'une classe ou d'un de ses descendants.

Syntaxe Java
objet instanceof Classe
Exemple — Utilisation de instanceof
Code
Professeur prof = new Professeur();
Personne p1 = new Personne();

System.out.println(prof instanceof Professeur);  // true
System.out.println(prof instanceof Personne);    // true
System.out.println(p1 instanceof Personne);      // true
System.out.println(p1 instanceof Professeur);    // false
Sortie
true
true
true
false
Explication : instanceof est vrai si l'opérande de gauche peut être converti vers le type de l'opérande de droite.

Redéfinition des méthodes de la superclasse

Redéfinition (Override)

La redéfinition consiste à fournir une nouvelle implémentation d'une méthode héritée dans une sous-classe. La méthode doit avoir la même signature (nom, paramètres, type de retour) que celle de la superclasse.

Utiliser le même nom de méthode pour différentes implémentations est appelé polymorphisme (du grec "plusieurs formes").

Redéfinition vs Surcharge
  • Redéfinition (Override) : même nom, même paramètres — remplace la méthode de la superclasse
  • Surcharge (Overload) : même nom, paramètres différents — ajoute une nouvelle méthode

L'annotation @Override

Bonne pratique

Utilisez l'annotation @Override avant toute méthode que vous souhaitez redéfinir. Cette annotation indique au compilateur votre intention et génère une erreur si la signature ne correspond pas à une méthode de la superclasse.

Exemple 2 — Redéfinition avec @Override
Code complet
class Personne {
    private String nom;
    private String cin;
    
    public Personne(String nom, String cin) {
        this.nom = nom;
        this.cin = cin;
    }
    
    public void afficher() {
        System.out.println("Nom : " + nom + " - CIN : " + cin);
    }
}

public class Professeur extends Personne {
    private int ppr;
    private double salaire;
    
    public Professeur(String nom, String cin, int ppr, double salaire) {
        super(nom, cin);  // Appel au constructeur parent
        this.ppr = ppr;
        this.salaire = salaire;
    }
    
    @Override
    public void afficher() {
        System.out.println("PPR : " + ppr + " - Salaire : " + salaire);
    }
    
    public static void main(String[] args) {
        Professeur p = new Professeur("Mostafa", "D4566", 3423, 7000.5);
        p.afficher();
    }
}
Sortie
PPR : 3423 - Salaire : 7000.5

Constructeurs et héritage

Ordre d'exécution des constructeurs

Lorsque vous instanciez un objet de sous-classe, le constructeur de la superclasse s'exécute d'abord, puis le constructeur de la sous-classe.

Exemple 3 — Ordre d'exécution
Code
class Personne {
    public Personne() {
        System.out.println("Constructeur de Personne");
    }
}

public class Professeur extends Personne {
    public Professeur() {
        System.out.println("Constructeur de Professeur");
    }
    
    public static void main(String[] args) {
        Professeur p = new Professeur();
    }
}
Sortie
Constructeur de Personne
Constructeur de Professeur

Le mot-clé super()

Lorsque la superclasse ne possède pas de constructeur sans paramètre, la sous-classe doit appeler explicitement un constructeur de la superclasse avec super(paramètres).

Syntaxe — Appel à super() Java
super(liste des paramètres);
Règle importante

L'instruction super() doit être la première instruction de tout constructeur de sous-classe qui l'utilise.

Exemple 4 — Appel explicite avec super()
Code
class Personne {
    private String nom;
    private String cin;
    
    public Personne(String nom, String cin) {
        this.nom = nom;
        this.cin = cin;
    }
}

public class Professeur extends Personne {
    private int ppr;
    private double salaire;
    
    public Professeur(String nom, String cin, int ppr, double salaire) {
        super(nom, cin);  // Doit être la première instruction
        this.ppr = ppr;
        this.salaire = salaire;
    }
    
    public static void main(String[] args) {
        Professeur p = new Professeur("Mostafa", "D4566", 3423, 7000.5);
    }
}

Conditions d'utilisation des constructeurs

Si la superclasse contient : Alors ses sous-classes :
Aucun constructeur défini par le programmeur
(constructeur par défaut automatique)},
Ne nécessitent pas de constructeurs explicites},
Un constructeur par défaut défini par le programmeur}, Ne nécessitent pas de constructeurs explicites},
Uniquement des constructeurs avec paramètres}, Doivent contenir un constructeur qui appelle super(…)},

Accéder aux méthodes de la superclasse

Si une méthode a été redéfinie mais que vous souhaitez utiliser la version de la superclasse dans la sous-classe, utilisez le mot-clé super :

Syntaxe — Appel à super.méthode() Java
super.nomMethode(paramètres);
Exemple 5 — Appel à la méthode de la superclasse
Code
class Personne {
    private String nom;
    private String cin;
    
    public Personne(String nom, String cin) {
        this.nom = nom;
        this.cin = cin;
    }
    
    public void afficher() {
        System.out.println("Nom : " + nom + " - CIN : " + cin);
    }
}

public class Professeur extends Personne {
    private int ppr;
    private double salaire;
    
    public Professeur(String nom, String cin, int ppr, double salaire) {
        super(nom, cin);
        this.ppr = ppr;
        this.salaire = salaire;
    }
    
    @Override
    public void afficher() {
        super.afficher();  // Appel à la méthode de Personne
        System.out.println("PPR : " + ppr + " - Salaire : " + salaire);
    }
    
    public static void main(String[] args) {
        Professeur p = new Professeur("Mostafa", "D4566", 3423, 7000.5);
        p.afficher();
    }
}
Sortie
Nom : Mostafa - CIN : D4566
PPR : 3423 - Salaire : 7000.5
Remarque sur super

L'appel à super.méthode() n'a pas besoin d'être la première instruction de la méthode, contrairement à l'appel au constructeur super().

Protection des données — Le modificateur protected

Modificateur protected

Le modificateur protected offre un niveau d'accès intermédiaire entre private et public :

  • Accessible dans la classe elle-même
  • Accessible dans toutes les sous-classes (même dans un autre package)
  • Accessible dans le même package
Hiérarchie des modificateurs d'accès
  • private : accessible uniquement dans la classe
  • protected : accessible dans la classe, les sous-classes et le même package
  • public : accessible partout
  • (package-private) : accessible dans le même package (absence de modificateur)

Méthodes qui ne peuvent pas être redéfinies

Trois types de méthodes ne peuvent pas être redéfinies dans une sous-classe :

  • Les méthodes static (appartiennent à la classe, pas à l'instance)
  • Les méthodes final (verrouillent l'implémentation)
  • Les méthodes d'une classe final (une classe finale ne peut pas avoir de sous-classes)
Exemple — Méthode finale Java
public class Personne {
    // Cette méthode ne peut pas être redéfinie
    public final void methodeNonRedefinissable() {
        System.out.println("Ne peut pas être modifiée");
    }
}

Exemple complet — Bonnes pratiques

Exemple complet avec encapsulation

/**
 * Classe de base représentant une personne
 */
public class Personne {
    private String nom;
    private String cin;
    private int age;
    
    /**
     * Constructeur avec paramètres
     * @param nom Nom de la personne
     * @param cin CIN de la personne
     * @param age Âge de la personne
     */
    public Personne(String nom, String cin, int age) {
        this.nom = nom;
        this.cin = cin;
        this.age = age;
    }
    
    // Getters et setters
    public String getNom() {
        return nom;
    }
    
    public void setNom(String nom) {
        this.nom = nom;
    }
    
    public String getCin() {
        return cin;
    }
    
    public void setCin(String cin) {
        this.cin = cin;
    }
    
    public int getAge() {
        return age;
    }
    
    public void setAge(int age) {
        if (age >= 0) {
            this.age = age;
        }
    }
    
    /**
     * Affiche les informations de la personne
     */
    public void afficher() {
        System.out.printf("Nom: %s | CIN: %s | Âge: %d%n", nom, cin, age);
    }
    
    @Override
    public String toString() {
        return String.format("Personne{nom='%s', cin='%s', age=%d}", nom, cin, age);
    }
}
/**
 * Classe dérivée représentant un professeur
 */
public class Professeur extends Personne {
    private int ppr;           // Numéro PPR
    private double salaire;    // Salaire mensuel
    private String specialite;  // Spécialité
    
    /**
     * Constructeur complet
     */
    public Professeur(String nom, String cin, int age, 
                      int ppr, double salaire, String specialite) {
        super(nom, cin, age);  // Appel au constructeur parent
        this.ppr = ppr;
        this.salaire = salaire;
        this.specialite = specialite;
    }
    
    // Getters et setters spécifiques
    public int getPpr() {
        return ppr;
    }
    
    public void setPpr(int ppr) {
        this.ppr = ppr;
    }
    
    public double getSalaire() {
        return salaire;
    }
    
    public void setSalaire(double salaire) {
        if (salaire >= 0) {
            this.salaire = salaire;
        }
    }
    
    public String getSpecialite() {
        return specialite;
    }
    
    public void setSpecialite(String specialite) {
        this.specialite = specialite;
    }
    
    /**
     * Calcule le salaire annuel
     */
    public double calculerSalaireAnnuel() {
        return salaire * 12;
    }
    
    @Override
    public void afficher() {
        super.afficher();  // Appel à la méthode parent
        System.out.printf("PPR: %d | Salaire: %.2f DH | Spécialité: %s%n", 
                          ppr, salaire, specialite);
    }
    
    @Override
    public String toString() {
        return String.format("Professeur{ppr=%d, salaire=%.2f, specialite='%s', %s}", 
                            ppr, salaire, specialite, super.toString());
    }
}
/**
 * Classe de test
 */
public class Main {
    public static void main(String[] args) {
        // Création d'un professeur
        Professeur prof = new Professeur(
            "Mostafa El Alaoui", 
            "D456678", 
            35, 
            3423, 
            7500.0, 
            "Informatique"
        );
        
        // Utilisation des méthodes
        System.out.println("=== Informations du professeur ===");
        prof.afficher();
        
        System.out.println("\n=== Détails supplémentaires ===");
        System.out.println("Salaire annuel: " + prof.calculerSalaireAnnuel() + " DH");
        
        // Polymorphisme
        System.out.println("\n=== Polymorphisme ===");
        Personne p = new Professeur("Fatima Zahra", "E789012", 42, 5678, 8200.0, "Mathématiques");
        p.afficher();  // La version de Professeur est appelée
        
        // Vérification des types
        System.out.println("\n=== Vérification des types ===");
        System.out.println("prof instanceof Professeur: " + (prof instanceof Professeur));
        System.out.println("prof instanceof Personne: " + (prof instanceof Personne));
        System.out.println("p instanceof Professeur: " + (p instanceof Professeur));
    }
}
Sortie
=== Informations du professeur ===
Nom: Mostafa El Alaoui | CIN: D456678 | Âge: 35
PPR: 3423 | Salaire: 7500,00 DH | Spécialité: Informatique

=== Détails supplémentaires ===
Salaire annuel: 90000,0 DH

=== Polymorphisme ===
Nom: Fatima Zahra | CIN: E789012 | Âge: 42
PPR: 5678 | Salaire: 8200,00 DH | Spécialité: Mathématiques

=== Vérification des types ===
prof instanceof Professeur: true
prof instanceof Personne: true
p instanceof Professeur: true

Tableau récapitulatif des mots-clés

Mot-clé Rôle
extends\\ Établit une relation d'héritage entre deux classes},
super()\\ Appelle le constructeur de la superclasse (doit être la première instruction)},
super.méthode()\\ Appelle une méthode de la superclasse (même si elle est redéfinie)},
@Override\\ Annotation indiquant qu'une méthode redéfinit une méthode de la superclasse},
instanceof\\ Vérifie si un objet est une instance d'une classe donnée},

L'essentiel en bref

Points clés à retenir
  • L'héritage permet à une classe (sous-classe) d'acquérir les attributs et méthodes d'une autre classe (superclasse).
  • Utilisez le mot-clé extends pour créer une sous-classe.
  • La relation "est un" (instanceof) est vraie pour une sous-classe par rapport à sa superclasse.
  • La redéfinition permet à une sous-classe de fournir sa propre implémentation d'une méthode héritée (annotation @Override).
  • Le constructeur de la superclasse s'exécute toujours avant celui de la sous-classe.
  • Utilisez super() pour appeler un constructeur paramétré de la superclasse.
  • Le modificateur protected permet l'accès aux sous-classes.
  • Distinction importante : héritage ("est un") vs composition/agrégation ("a un").
Bonnes pratiques
  • Privilégiez la composition sur l'héritage quand la relation "est un" n'est pas clairement établie.
  • Utilisez l'encapsulation (attributs private) avec des getters/setters.
  • Toujours utiliser @Override lors de la redéfinition de méthodes.
  • Évitez les chaînes d'héritage trop profondes (préférez une hiérarchie plate).
  • Documentez si une méthode est destinée à être redéfinie.
Un peu d'histoire

Le concept d'héritage en programmation orientée objet trouve ses origines dans le langage Simula (1967), développé par Ole-Johan Dahl et Kristen Nygaard. Java a repris ce concept en l'affinant avec l'utilisation de l'héritage simple (une classe ne peut hériter que d'une seule superclasse), contrairement au C++ qui supporte l'héritage multiple. Cette limitation simplifie le modèle mémoire et évite les problèmes de "diamond inheritance".

Sortie
// La sortie apparaîtra ici…
Prêt · Ctrl+Entrée pour exécuter

Discussion (0)

Soyez le premier à laisser un commentaire !

Laisser un commentaire

Votre commentaire sera visible après modération.