Introduction au Concept d’Héritage en Java
L’héritage est l’un des piliers fondamentaux de la programmation orientée objet (POO). Il permet la création de relations entre les classes, favorisant ainsi la réutilisation du code, la modularité et une conception logique des applications. En Java, l’héritage est un mécanisme essentiel qui permet à une classe d’hériter des attributs et des méthodes d’une autre classe, appelée superclasse ou classe de base.
Fonctionnement de l’Héritage
L’héritage en Java est basé sur une relation « est-un ». Cela signifie qu’une classe dérivée, appelée sous-classe, est une version plus spécifique de sa classe parente. Par exemple, si nous avons une classe Animal
comme superclasse, nous pouvons avoir des sous-classes telles que Chien
, Chat
et Oiseau
, qui héritent toutes des attributs et des méthodes de la classe Animal
.
Exemple d’Héritage en Java
Considérons un exemple simple où nous avons une classe Animal
comme superclasse et deux sous-classes Chien
et Chat
. La classe Animal
peut avoir des attributs comme nom
et des méthodes comme manger()
, tandis que les sous-classes Chien
et Chat
peuvent avoir des attributs et des méthodes spécifiques à ces types d’animaux.
// Classe Animal (superclasse)
public class Animal {
protected String nom;
public Animal(String nom) {
this.nom = nom;
}
public void manger() {
System.out.println(nom + " mange de la nourriture.");
}
}
// Sous-classe Chien
public class Chien extends Animal {
public Chien(String nom) {
super(nom);
}
public void aboyer() {
System.out.println(nom + " aboie.");
}
}
// Sous-classe Chat
public class Chat extends Animal {
public Chat(String nom) {
super(nom);
}
public void miauler() {
System.out.println(nom + " miaule.");
}
}
Dans cet exemple, la classe Chien
et la classe Chat
héritent de la classe Animal
, ce qui leur permet d’accéder aux attributs et méthodes définis dans la classe Animal
. De plus, chaque sous-classe peut avoir ses propres méthodes spécifiques, comme aboyer()
pour un chien et miauler()
pour un chat.
Représentation UML de l’Héritage
Dans le langage de modélisation UML (Unified Modeling Language), l’héritage est représenté à l’aide de diagrammes de classes. Un diagramme de classes UML illustre les relations et les structures entre les différentes classes dans un système logiciel.
Dans un diagramme de classes UML, l’héritage est représenté par une flèche pointant de la sous-classe vers la superclasse. La flèche est accompagnée du mot-clé « extends » pour indiquer la relation d’héritage entre les deux classes.
Voici un exemple simplifié :
+--------------+ +----------------+
| Animal | <-------- | Chien |
+--------------+ +----------------+
Dans cet exemple, la classe Chien
hérite de la classe Animal
. La flèche pointe de Chien
vers Animal
, ce qui signifie que Chien
est une sous-classe de Animal
. La notation extends
n’est pas affichée dans le diagramme UML, mais elle est implicite dans la flèche de l’héritage.
Exemple de Diagramme de Classes UML avec Héritage
Considérons un exemple plus concret où nous avons une hiérarchie de classes pour représenter différents types de véhicules. Voici à quoi pourrait ressembler un diagramme de classes UML pour cette situation :
+------------------+ +------------------------+
| Véhicule | <-------- | Voiture |
+------------------+ +------------------------+
| | | - nombreRoues: int |
| - marque: String| | - couleur: String |
| - modèle: String| | + démarrer(): void |
+------------------+ | + arrêter(): void |
+------------------------+
^
|
+------------------------+
| Moto |
+------------------------+
| - cylindrée: int |
| - annéeFabrication: int|
| + accélérer(): void |
| + freiner(): void |
+------------------------+
Dans ce diagramme, la classe Voiture
et la classe Moto
héritent toutes deux de la classe Véhicule
. Cela signifie que Voiture
et Moto
partagent les attributs et les méthodes définis dans la classe Véhicule
, comme la marque et le modèle. En outre, chaque sous-classe peut avoir ses propres attributs et méthodes spécifiques, comme nombreRoues
pour Voiture
et cylindrée
pour Moto
.
Et que vient faire la classe java.lang.Object dans l’histoire ?
La classe java.lang.Object
joue un rôle central dans le cadre de l’héritage en Java. En fait, toutes les classes Java, explicites ou implicites, héritent directement ou indirectement de la classe Object
. Voici ce que cela signifie et comment cela impacte la conception de votre programme :
Importance de la classe java.lang.Object
- Rôle Fondamental :
java.lang.Object
est la classe racine de la hiérarchie de classes Java. Elle est au sommet de la chaîne d’héritage de toutes les autres classes Java. - Méthodes de Base : La classe
Object
définit plusieurs méthodes fondamentales que toutes les autres classes héritent :toString()
: Pour représenter l’objet sous forme de chaîne de caractères.equals(Object obj)
: Pour comparer l’égalité de deux objets.hashCode()
: Pour retourner un code de hachage pour l’objet.getClass()
: Pour obtenir la classe de l’objet.clone()
: Pour créer une copie de l’objet.finalize()
: Pour effectuer des actions avant la destruction de l’objet par le ramasse-miettes.
- Héritage Implicite : Si une classe ne spécifie pas une classe parente avec le mot-clé
extends
, elle hérite automatiquement deObject
. - Polymorphisme : La classe
Object
est souvent utilisée lorsqu’un code doit traiter un objet de manière générique sans connaître son type réel, grâce au polymorphisme. - Méthode
toString()
: Il est courant de redéfinir la méthodetoString()
dans les classes que vous créez pour fournir une représentation textuelle significative de l’objet.
Exemple d’Utilisation de Object
public class Exemple {
public static void main(String[] args) {
// Création d'un objet de type String
String str = "Bonjour";
// Utilisation de la méthode toString() héritée de Object
System.out.println(str.toString()); // Affiche "Bonjour"
// Comparaison de deux objets
String autreStr = "Bonjour";
System.out.println(str.equals(autreStr)); // Affiche true
}
}
Dans cet exemple, la classe String
hérite de Object
. Ainsi, nous pouvons utiliser les méthodes définies dans Object
comme toString()
et equals()
avec des objets de type String
.
Mise en oeuvre de l’héritage
La mise en œuvre de l’héritage en Java implique la création de relations de parenté entre les classes, permettant ainsi la réutilisation du code et la spécialisation des fonctionnalités. Voici comment vous pouvez mettre en œuvre l’héritage dans vos programmes Java :
Utilisation du Mot-Clé extends
En Java, l’héritage est réalisé en utilisant le mot-clé extends
. Voici un exemple simple de mise en œuvre de l’héritage :
// Superclasse (ou classe parente)
public class Animal {
public void manger() {
System.out.println("L'animal mange.");
}
}
// Sous-classe (ou classe fille)
public class Chien extends Animal {
public void aboyer() {
System.out.println("Le chien aboie.");
}
}
Dans cet exemple, la classe Chien
hérite de la classe Animal
à l’aide du mot-clé extends
. Cela signifie que la classe Chien
hérite de toutes les méthodes publiques et protégées de la classe Animal
, y compris la méthode manger()
.
Enrichissement de la Classe avec des Spécificités
Une fois que vous avez hérité d’une classe, vous pouvez enrichir la sous-classe en ajoutant de nouvelles méthodes et attributs spécifiques. Par exemple :
// Ajout d'attributs et de méthodes spécifiques à la sous-classe Chien
public class Chien extends Animal {
private String race;
public Chien(String race) {
this.race = race;
}
public void aboyer() {
System.out.println("Le chien de race " + race + " aboie.");
}
}
Dans cette version, la classe Chien
possède un attribut race
et un constructeur pour initialiser cet attribut. Elle a également une méthode spécifique aboyer()
qui affiche le type de race du chien.
Définition de Constructeurs et Utilisation du Mot-Clé super
Lorsque vous définissez des constructeurs dans une sous-classe, vous pouvez utiliser le mot-clé super
pour appeler le constructeur de la classe parente et initialiser ses attributs. Par exemple :
// Définition d'un constructeur dans la sous-classe Chien
public class Chien extends Animal {
private String race;
public Chien(String nom, String race) {
super(nom); // Appel du constructeur de la classe parente
this.race = race;
}
}
Dans cet exemple, le constructeur de la classe Chien
appelle d’abord le constructeur de la classe Animal
en utilisant super(nom)
pour initialiser le nom de l’animal, puis il initialise l’attribut race
spécifique à la classe Chien
.
Assistance Eclipse à la Production de Constructeurs
Assistance IntelliJ à la Production de Constructeurs
Redéfinition de Méthode en Java
En Java, pour redéfinir une méthode héritée dans une sous-classe, vous devez déclarer une méthode avec la même signature (nom et paramètres) que la méthode de la superclasse. Voici un exemple :
// Superclasse
public class Animal {
public void faireDuBruit() {
System.out.println("L'animal fait un bruit.");
}
}
// Sous-classe qui redéfinit la méthode faireDuBruit()
public class Chien extends Animal {
@Override
public void faireDuBruit() {
System.out.println("Le chien aboie.");
}
}
Dans cet exemple, la classe Chien
redéfinit la méthode faireDuBruit()
héritée de la classe Animal
. En utilisant l’annotation @Override
, nous indiquons explicitement au compilateur que cette méthode est une redéfinition d’une méthode de la superclasse. Lorsque vous appelez la méthode faireDuBruit()
sur un objet de type Chien
, la version spécifique de la méthode dans la classe Chien
est exécutée.
Utilisation de l’Annotation @Override
L’utilisation de l’annotation @Override
est facultative mais fortement recommandée lors de la redéfinition de méthodes. Elle permet de détecter les erreurs de syntaxe potentielles et de garantir que vous redéfinissez correctement une méthode héritée. Si vous utilisez l’annotation @Override
et qu’il y a une erreur dans votre signature de méthode (par exemple, une faute de frappe dans le nom de la méthode), le compilateur générera une erreur.
Appel d’une Méthode de la Classe Parente, Redéfinie par la Classe Fille
Lorsque vous redéfinissez une méthode dans une sous-classe, vous pouvez toujours appeler la version de la méthode de la superclasse en utilisant le mot-clé super
. Voici un exemple :
// Sous-classe qui redéfinit la méthode faireDuBruit() et appelle la version de la superclasse
public class Chien extends Animal {
@Override
public void faireDuBruit() {
super.faireDuBruit(); // Appel de la méthode de la superclasse
System.out.println("Le chien aboie.");
}
}
Dans cet exemple, la méthode faireDuBruit()
de la superclasse Animal
est appelée en premier à l’aide de super.faireDuBruit()
, puis la sous-classe Chien
ajoute son propre comportement en imprimant « Le chien aboie. ».
Le polymorphisme est un concept fondamental de la programmation orientée objet (POO) qui permet à une même méthode d’avoir des comportements différents en fonction du type de l’objet sur lequel elle est appelée. Cela permet de traiter des objets de différentes classes de manière uniforme, ce qui favorise la réutilisation du code et la flexibilité de conception. Voici comment le polymorphisme est implémenté en Java :
Définition du Polymorphisme
En Java, le polymorphisme se produit lorsque vous appelez une méthode sur un objet et que le comportement de cette méthode dépend du type de l’objet. Il existe deux formes principales de polymorphisme en Java : le polymorphisme statique (lié au temps de compilation) et le polymorphisme dynamique (lié au temps d’exécution).
- Polymorphisme Statique : Le polymorphisme statique, également appelé liaison tardive ou liaison dynamique, se produit lorsque la méthode à exécuter est déterminée au moment de l’exécution du programme. Cela permet à une méthode d’être appelée de manière polymorphe à condition qu’elle soit redéfinie dans les sous-classes. Cela favorise la flexibilité et l’évolutivité du code.
Dans le polymorphisme statique, la méthode à appeler est déterminée au moment de l’exécution du programme, en fonction du type réel de l’objet.
class Animal {
void faireDuBruit() {
System.out.println("L'animal fait un bruit.");
}
}
class Chien extends Animal {
void faireDuBruit() {
System.out.println("Le chien aboie.");
}
}
class Chat extends Animal {
void faireDuBruit() {
System.out.println("Le chat miaule.");
}
}
public class Main {
public static void main(String[] args) {
Animal monAnimal = new Chien(); // Polymorphisme statique
monAnimal.faireDuBruit(); // Appelle la méthode faireDuBruit() de Chien
}
}
- Polymorphisme Dynamique : Le polymorphisme dynamique, également appelé liaison précoce ou liaison statique, se produit lorsque la méthode à exécuter est déterminée au moment de la compilation. Cela se produit lorsque vous appelez une méthode sur un objet et que Java sélectionne la méthode appropriée à exécuter en fonction du type déclaré de l’objet, et non du type réel de l’objet.
Dans le polymorphisme dynamique, la méthode à appeler est déterminée au moment de la compilation du programme, en fonction du type déclaré de la référence.
class Animal {
void faireDuBruit() {
System.out.println("L'animal fait un bruit.");
}
}
class Chien extends Animal {
void faireDuBruit() {
System.out.println("Le chien aboie.");
}
}
class Chat extends Animal {
void faireDuBruit() {
System.out.println("Le chat miaule.");
}
}
public class Main {
public static void main(String[] args) {
Animal monAnimal = new Chien(); // Polymorphisme dynamique
monAnimal.faireDuBruit(); // Appelle la méthode faireDuBruit() de Chien (liaison précoce)
}
}
Dans cet exemple, la référence monAnimal
est de type Animal
. Même si l’objet réel est un Chien
, la méthode faireDuBruit()
est sélectionnée au moment de la compilation en fonction du type déclaré de la référence (Animal
). C’est ce qu’on appelle la liaison précoce ou polymorphisme dynamique.
Utilisation de l’opérateur instanceof
L’opérateur instanceof
est un opérateur en Java qui permet de vérifier si un objet est une instance d’une classe spécifique, ou d’une classe dérivée de cette classe. Cela permet de réaliser des opérations conditionnelles en fonction du type réel d’un objet. Voici comment utiliser l’opérateur instanceof
en Java :
Syntaxe de l’Opérateur instanceof
L’opérateur instanceof
s’utilise de la manière suivante :
objet instanceof Classe
Cet opérateur retourne true
si l’objet est une instance de la classe spécifiée, ou d’une sous-classe de cette classe. Sinon, il retourne false
.
Exemple d’Utilisation de l’Opérateur instanceof
Considérons un exemple où nous avons une classe Animal
avec deux sous-classes Chien
et Chat
. Nous voulons vérifier le type réel d’un objet et agir en conséquence:
class Animal {}
class Chien extends Animal {}
class Chat extends Animal {}
public class Main {
public static void main(String[] args) {
Animal monAnimal = new Chien(); // Création d'un objet de type Chien
// Vérification du type réel de monAnimal
if (monAnimal instanceof Chien) {
System.out.println("C'est un chien !");
} else if (monAnimal instanceof Chat) {
System.out.println("C'est un chat !");
} else if (monAnimal instanceof Animal) {
System.out.println("C'est un animal !");
} else {
System.out.println("Type inconnu !");
}
}
}
Dans cet exemple, nous créons un objet de type Chien
et nous vérifions son type réel en utilisant l’opérateur instanceof
. En fonction du résultat de cette vérification, nous affichons un message approprié.
Utilisation Pratique
L’opérateur instanceof
est souvent utilisé en conjonction avec le polymorphisme pour effectuer des actions spécifiques en fonction du type réel des objets. Cela peut être utile dans des situations telles que la manipulation de collections d’objets de types différents, ou lors de la gestion de méthodes polymorphiques.
Travaux pratiques
Exercice 1 : Gestion d’Articles dans un Magasin
- Créez une classe
Article
avec les attributsreference
,designation
etprixUnitaire
. - Créez deux sous-classes de
Article
:ArticleAlimentaire
etArticleElectromenager
. Ajoutez des attributs spécifiques commedatePeremption
pour l’article alimentaire etgarantie
pour l’article électroménager. - Implémentez une méthode
afficherDetails()
dans chaque sous-classe pour afficher les détails de l’article, y compris les attributs spécifiques. - Créez une classe
GestionMagasin
avec une méthodemain()
pour tester vos implémentations. Créez des objets de typeArticleAlimentaire
etArticleElectromenager
, appelez la méthodeafficherDetails()
et affichez les détails de chaque article.
Correction
1. Création de la classe Article :
public class Article {
private String reference;
private String designation;
private double prixUnitaire;
// Constructeur
public Article(String reference, String designation, double prixUnitaire) {
this.reference = reference;
this.designation = designation;
this.prixUnitaire = prixUnitaire;
}
// Getters et Setters
public String getReference() {
return reference;
}
public void setReference(String reference) {
this.reference = reference;
}
public String getDesignation() {
return designation;
}
public void setDesignation(String designation) {
this.designation = designation;
}
public double getPrixUnitaire() {
return prixUnitaire;
}
public void setPrixUnitaire(double prixUnitaire) {
this.prixUnitaire = prixUnitaire;
}
// Méthode pour afficher les détails de l'article
public void afficherDetails() {
System.out.println("Référence : " + reference);
System.out.println("Désignation : " + designation);
System.out.println("Prix unitaire : " + prixUnitaire);
}
}
2. Création de la sous-classes ArticleAlimentaire :
public class ArticleAlimentaire extends Article {
private String datePeremption;
// Constructeur
public ArticleAlimentaire(String reference, String designation, double prixUnitaire, String datePeremption) {
super(reference, designation, prixUnitaire);
this.datePeremption = datePeremption;
}
// Getter et Setter
public String getDatePeremption() {
return datePeremption;
}
public void setDatePeremption(String datePeremption) {
this.datePeremption = datePeremption;
}
// Méthode pour afficher les détails de l'article alimentaire
@Override
public void afficherDetails() {
super.afficherDetails();
System.out.println("Date de péremption : " + datePeremption);
}
}
3. Création de la sous-classes ArticleElectromenager :
public class ArticleElectromenager extends Article {
private int garantie;
// Constructeur
public ArticleElectromenager(String reference, String designation, double prixUnitaire, int garantie) {
super(reference, designation, prixUnitaire);
this.garantie = garantie;
}
// Getter et Setter
public int getGarantie() {
return garantie;
}
public void setGarantie(int garantie) {
this.garantie = garantie;
}
// Méthode pour afficher les détails de l'article électroménager
@Override
public void afficherDetails() {
super.afficherDetails();
System.out.println("Garantie (en mois) : " + garantie);
}
}
4. Création de la classe TestMagasin pour tester les implémentations :
public class TestMagasin {
public static void main(String[] args) {
// Création d'un article alimentaire
ArticleAlimentaire articleAlimentaire = new ArticleAlimentaire("REF001", "Pain", 2.5, "10/01/2024");
System.out.println("Détails de l'article alimentaire :");
articleAlimentaire.afficherDetails();
System.out.println();
// Création d'un article électroménager
ArticleElectromenager articleElectromenager = new ArticleElectromenager("REF002", "Lave-linge", 499.99, 24);
System.out.println("Détails de l'article électroménager :");
articleElectromenager.afficherDetails();
}
}
Remarques :
- Les classes
Article
,ArticleAlimentaire
etArticleElectromenager
ont été correctement implémentées avec des attributs appropriés, des constructeurs, des méthodes getters/setters et des méthodes d’affichage. - La méthode
afficherDetails()
a été correctement implémentée dans chaque sous-classe pour afficher les détails spécifiques de l’article, tout en appelant la méthodeafficherDetails()
de la superclasse pour afficher les attributs communs. - La classe
TestMagasin
a été utilisée pour tester les implémentations en créant des objets d’article alimentaire et électroménager, puis en appelant la méthodeafficherDetails()
pour afficher les détails de chaque article.
Exercice2 : Gestion des Personnes dans une École – Implémentation Java
Description : Dans cet exercice, vous allez mettre en pratique vos compétences en programmation Java en implémentant les classes d’un système de gestion des personnes dans une école. Vous disposerez déjà du diagramme de classes UML représentant la structure de classes pour le système.
Diagramme de classes UML :
+---------------------------------+
| Personne |
+---------------------------------+
| - nom: String |
| - prenom: String |
+---------------------------------+
| + afficherInfos(): void |
+---------------------------------+
^
|
|
+----------------------+
| Etudiant |
+----------------------+
| - matricule: String |
| - annee: int |
+----------------------+
^
|
|
+----------------------+
| Enseignant |
+----------------------+
| - diplome: String |
| - anciennete: int |
+----------------------+
Instructions :
- Implémentez les classes Java correspondant au diagramme de classes UML fourni. Vous aurez besoin des classes
Personne
,Etudiant
etEnseignant
. - Respectez la relation d’héritage entre les classes. Par exemple,
Etudiant
etEnseignant
doivent étendre la classePersonne
. - Ajoutez des attributs et des méthodes appropriés à chaque classe en vous basant sur le diagramme UML. Assurez-vous d’implémenter la méthode
afficherInfos()
dans chaque classe pour afficher les informations de chaque personne, étudiant ou enseignant. - Dans la méthode
main()
, créez des instances d’étudiants et d’enseignants, appelez la méthodeafficherInfos()
pour chaque personne et affichez leurs informations. - Testez votre implémentation avec différents étudiants et enseignants pour vérifier son bon fonctionnement.
Correction
Voici une implémentation détaillée en Java pour l’exercice donné :
- Classe
Personne
:
public class Personne {
private String nom;
private String prenom;
public Personne(String nom, String prenom) {
this.nom = nom;
this.prenom = prenom;
}
public void afficherInfos() {
System.out.println("Nom : " + nom);
System.out.println("Prénom : " + prenom);
}
}
Classe Etudiant
:
public class Etudiant extends Personne {
private String matricule;
private int annee;
public Etudiant(String nom, String prenom, String matricule, int annee) {
super(nom, prenom);
this.matricule = matricule;
this.annee = annee;
}
@Override
public void afficherInfos() {
super.afficherInfos();
System.out.println("Matricule : " + matricule);
System.out.println("Année : " + annee);
}
}
Classe Enseignant
:
public class Enseignant extends Personne {
private String diplome;
private int anciennete;
public Enseignant(String nom, String prenom, String diplome, int anciennete) {
super(nom, prenom);
this.diplome = diplome;
this.anciennete = anciennete;
}
@Override
public void afficherInfos() {
super.afficherInfos();
System.out.println("Diplôme : " + diplome);
System.out.println("Ancienneté : " + anciennete + " ans");
}
}
Méthode main()
pour tester :
public class Main {
public static void main(String[] args) {
Etudiant etudiant = new Etudiant("Dupont", "Jean", "12345", 2022);
Enseignant enseignant = new Enseignant("Smith", "Alice", "Doctorat en informatique", 5);
System.out.println("Informations sur l'étudiant :");
etudiant.afficherInfos();
System.out.println("\nInformations sur l'enseignant :");
enseignant.afficherInfos();
}
}
Explication :
- Nous avons créé une classe
Personne
avec des attributsnom
etprenom
, ainsi qu’une méthodeafficherInfos()
pour afficher les informations de la personne. - La classe
Etudiant
étend la classePersonne
et ajoute les attributsmatricule
etannee
. Elle redéfinit également la méthodeafficherInfos()
pour afficher les informations de l’étudiant. - La classe
Enseignant
fonctionne de la même manière que la classeEtudiant
, mais avec des attributsdiplome
etanciennete
. - Dans la méthode
main()
, nous créons des instances d’étudiant et d’enseignant, appelons la méthodeafficherInfos()
pour afficher leurs informations.
Interdire l’héritage ou la redéfinition de méthode
Pour interdire l’héritage ou la redéfinition de méthode dans Java, vous pouvez utiliser le mot-clé final
. Voici comment vous pouvez l’appliquer :
- Pour interdire l’héritage d’une classe : Vous pouvez déclarer la classe avec le mot-clé
final
. Cela signifie que la classe ne peut pas être étendue par d’autres classes.Exemple :
public final class MaClasse {
// Corps de la classe
}
Pour interdire la redéfinition d’une méthode : Vous pouvez déclarer la méthode avec le mot-clé final
. Cela signifie que la méthode ne peut pas être redéfinie par les sous-classes.
Exemple :
public class MaClasse {
public final void maMethode() {
// Corps de la méthode
}
}
Dans les deux cas, l’utilisation du mot-clé final
garantit que la classe ou la méthode ne peut pas être modifiée ou étendue, ce qui peut être utile dans certaines situations où vous voulez contrôler strictement le comportement de votre code.
Voici un exemple où l’interdiction d’héritage est appliquée en Java :
public final class Animal {
public void manger() {
System.out.println("L'animal mange.");
}
}
// La classe Chien tente d'étendre la classe Animal, mais elle ne peut pas
// car Animal est déclarée comme final.
public class Chien extends Animal {
// Ceci générera une erreur de compilation :
// "Cannot inherit from final Animal"
}
Dans cet exemple, la classe Animal
est déclarée comme final
, ce qui signifie qu’elle ne peut pas être étendue par d’autres classes. Par conséquent, lorsque nous essayons de créer une classe Chien
qui étend Animal
, une erreur de compilation se produira car l’héritage de la classe Animal
est interdit en raison de sa déclaration comme final
.
Les Classes Scellées
Les classes scellées sont une nouvelle fonctionnalité introduite dans Java à partir de la version 15. Elles permettent de restreindre l’héritage en spécifiant explicitement quelles sous-classes sont autorisées pour une classe donnée. Cela offre un contrôle précis sur la hiérarchie des classes et garantit que seules certaines sous-classes spécifiées peuvent être créées.
Déclaration d’une Classe Scellée :
Pour déclarer une classe comme scellée, utilisez le mot-clé sealed
devant la déclaration de classe.
public sealed class Animal permits Chien, Chat {
// Corps de la classe Animal
}
Dans cet exemple, Animal
est une classe scellée qui permet deux sous-classes spécifiques : Chien
et Chat
.
Déclaration des Sous-classes Autorisées :
Utilisez le mot-clé permits
pour spécifier les sous-classes autorisées pour une classe scellée.
public final class Chien extends Animal {
// Corps de la classe Chien
}
public non-sealed class Chat extends Animal {
// Corps de la classe Chat
}
Dans cet exemple, Chien
est une sous-classe autorisée de Animal
, déclarée comme final
, ce qui signifie qu’elle ne peut pas être étendue davantage. Chat
est également une sous-classe autorisée, mais elle est déclarée comme non-sealed
, ce qui signifie qu’elle peut avoir des sous-classes supplémentaires non spécifiées ici.
Utilisation des Classes Scellées :
Les classes scellées offrent un contrôle précis sur l’héritage et sont utiles lorsque vous souhaitez restreindre la création de sous-classes pour une classe donnée. Elles garantissent que la hiérarchie des classes reste conforme à la conception et facilite la maintenance du code en réduisant le risque de mauvaise utilisation des sous-classes.
Exemple d’Exercice Corrigé :
Exercice : Gestion des Véhicules – Implémentation Java avec Classes Scellées
Dans cet exercice, vous devez créer un système de gestion de véhicules en utilisant des classes scellées pour restreindre l’héritage.
- Déclarez une classe scellée
Vehicule
qui autorise les sous-classesVoiture
etMoto
. - Créez les sous-classes
Voiture
etMoto
avec des attributs et des méthodes appropriés. - Implémentez une méthode
demarrer()
dans chaque sous-classe pour démarrer le véhicule. - Créez une nouvelle sous-classe
Avion
en dehors du paquetage actuel et essayez de la faire hériter deVehicule
. Cela devrait générer une erreur de compilation en raison de la restriction imposée par la classe scellée.
Correction
package gestionvehicules;
public sealed class Vehicule permits Voiture, Moto {
// Corps de la classe Vehicule
}
public final class Voiture extends Vehicule {
public void demarrer() {
System.out.println("La voiture démarre !");
}
}
public final class Moto extends Vehicule {
public void demarrer() {
System.out.println("La moto démarre !");
}
}
// La classe Avion est située dans un paquetage différent
package autresvehicules;
// Cette tentative d'héritage de la classe Vehicule générera une erreur de compilation
public class Avion extends gestionvehicules.Vehicule {
// Corps de la classe Avion
}
public class Main {
public static void main(String[] args) {
Vehicule maVoiture = new Voiture();
Vehicule maMoto = new Moto();
maVoiture.demarrer(); // Affiche : La voiture démarre !
maMoto.demarrer(); // Affiche : La moto démarre !
}
}
Dans cet exemple, nous avons remplacé les animaux par des véhicules. Les classes Voiture
et Moto
sont maintenant des sous-classes de Vehicule
, qui est une classe scellée. Nous avons également ajouté une nouvelle classe Avion
dans un paquetage différent et tenté de la faire hériter de Vehicule
, ce qui génère une erreur de compilation. Cela illustre la restriction imposée par les classes scellées.