L'héritage en Java
Vous connaissez le concept d'héritage de toutes sortes de situations non programmables. Lorsque vous utilisez le terme héritage, vous pouvez penser à héritage génétique. Grâce à la biologie, vous savez que votre groupe sanguin et la couleur de vos yeux sont le produit de gènes hérités. de nombreux faits vous concernant, vos attributs ou «champs de données», sont hérités.
De même, les classes que vous créez dans des langages de programmation orientés objet peuvent hériter des données et des méthodes des classes existantes.
En Java et dans tous les langages orientés objet, l'héritage est un mécanisme qui permet à une classe d’acquérir tous les comportements et attributs d’une autre classe, ce qui signifie que 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.
Lorsque vous créez une classe en la faisant hériter d'une autre classe, la nouvelle classe contient automatiquement les champs de données et les méthodes de la classe d'origine.
Terminologie de l'héritage
Une classe utilisée comme base d'héritage, telle que Personne, est une classe de base. Lorsque vous créez une classe qui hérite d'une classe de base (telle que Professeur), il s'agit d'une classe dérivée.
Ne confondez pas la situation “est une” situation avec "a une". “est une" décrit l'héritage, mais "a un" décrit le confinement.
- Par exemple, vous pouvez créer une classe Entreprise contenant un tableau d'objets Departments. Vous ne diriez pas: "Un département est une entreprise", mais vous diriez: "Une entreprise a des départements." Par conséquent, cette relation n'est pas un héritage; c'est une forme de confinement appelée composition - relation dans laquelle une classe contient un ou plusieurs membres d'une autre classe, lorsque ces membres ne continueraient pas d'exister sans l'objet qui les contient. (Par exemple, si une entreprise ferme, ses départements le font également.)
- De même, chaque objet Department peut contenir un tableau d'objets Employe. Dans ce cas, vous ne diriez pas «un employé est un département» mais vous diriez «un département a des employés». Cette relation n’est pas non plus un héritage; il s'agit d'un type spécifique de confinement appelé agrégation: relation dans laquelle une classe contient un ou plusieurs membres d'une autre classe, lorsque ces membres continueraient d'exister sans l'objet qui les contient. (Par exemple, si une entreprise ou un département est fermé, les employés continueront d'exister.)
Vous pouvez utiliser les termes superclasse et sous-classe comme synonymes de la classe de base et de la classe dérivée, respectivement. Ainsi, Professeur peut être appelée une sous-classe de la superclasse Personne. Vous pouvez également utiliser les termes classe mère et classe enfant. Un professeur est un enfant de la classe mère Personne.
Extends
Vous utilisez le mot clé extends pour réaliser l'héritage en Java. Par exemple, l'en-tête de classe suivante crée une relation superclasse – sous-classe entre Personne et Professeur:
public class Professeur extends Personne{ }
Chaque Professeur reçoit automatiquement les attributs et les méthodes du superclass Personne; vous ajoutez ensuite de nouveaux attributs et méthodes à la sous-classe nouvellement créée.
Exemple 1 :
public class Professeur extends Personne{ double salaire; int PPR; public double getSalaire(){ return salaire; } // ... autres méthodes }
Vous pouvez écrire une instruction qui instancie un objet de classe dérivée, telle que:
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 une proposition à sens unique; un enfant hérite d'une classe mère, mais pas l'inverse. Lorsque vous instanciez un objet Personne, il n'a pas accès aux méthodes de Professeur. Il est logique qu’un objet de classe mère n’ait pas accès aux données et aux méthodes de son enfant. Lorsque vous créez la classe mère, vous ne savez pas combien de sous-classes elle pourrait avoir à l'avenir, ni à quoi leurs attributs ou méthodes pourraient ressembler.
Vous pouvez utiliser l'opérateur instanceof pour déterminer si un objet est un membre ou un descendant d'une classe. Par exemple, si prof est un objet Professeur, la valeur de chacune des expressions suivantes est true:
prof instanceof Professeur prof instanceof Personne
Si p1 est un objet Personne, ce qui suit est vrai:
p1 instanceof Personne
Cependant, ce qui suit est faux:
p1 instanceof Professeur
instanceof est vraie si l'opérande de gauche peut être converti vers l'opérande de droite.
Redefinition des méthodes de la superclasse
Lorsque vous créez une sous-classe en étendant une classe existante, la nouvelle sous-classe contient des données et des méthodes définies dans la superclasse d'origine. En d'autres termes, tout objet de classe enfant possède tous les attributs de son classe mère. Parfois, cependant, les attributs et les méthodes de la superclasse ne conviennent pas entièrement aux objets de la sous-classe; dans ces cas, vous souhaitez redéfinir les membres de la classe mère.
redéfinir un champ ou une méthode dans une classe enfant signifie utiliser la version de classe enfant au lieu de la version du classe mère.
Utiliser le même nom de méthode pour indiquer différentes implémentations est appelé polymorphisme, terme signifiant beaucoup de formes - de nombreuses formes d'action différentes se produisent, même si vous utilisez le même mot pour décrire l'action. En d'autres termes, de nombreuses formes du même mot de description d'action existent, en fonction de l'objet associé.
Il est important de noter que chaque méthode de sous-classe redéfinie toute méthode de la classe mère qui a le même nom et la même liste de paramètres. Si la méthode de la classe mère a le même nom mais une liste de paramètres différente, la méthode de la sous-classe ne redéfinie pas la version de la classe mère; à la place, la méthode de sous-classe surcharge la méthode de classe mère et tout objet de sous-classe a accès aux deux versions.
Utilisation de l'annotation @Override
Lorsque vous redéfinir une méthode de classe mère dans une classe enfant, vous pouvez insérer une annotation de @Override juste avant la méthode. L'annotation de redéfinition indique au compilateur que votre intention est de redéfinir une méthode de la classe mère plutôt que de créer une méthode avec une nouvelle signature. Par exemple, si la classe Personne contient une méthode afficher() qui affiche le nom et le cin et que vous souhaitez redéfinir la méthode dans la classe enfant Professeur pour afficher le PPR et le salaire, vous pouvez écrire la méthode de classe enfant comme suit:
@Override public void afficher() { System.out.println(" PPR : " + PPR + " - Salaire : " + salaire); }
Exemple 2 :
class Personne { String nom; 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 { int PPR; 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() { System.out.println(" PPR : " + PPR + " - Salaire : " + salaire); } public static void main(String args[]) { Professeur p = new Professeur("Mostafa", "D4566", 3423, 7000.5); p.afficher(); } }
L'annotation @Override située avant l’en-tête de la méthode annonce votre intention de redéfinir une méthode de la classe mère et force le compilateur à émettre un message d’erreur si vous ne l’avez probablement pas fait car vous avez commis une erreur typographique dans l’en-tête de la méthode de la classe enfant (ne correspond pas à la version de la classe mère). Un programme fonctionnera et remplacera correctement les méthodes de la classe mère sans l'annotation @Override, mais leur utilisation peut vous aider à éviter les erreurs et constitue une forme de documentation.
Appel de constructeurs pendant l'héritage
Lorsque vous créez un objet, comme dans l'instruction suivante, vous appelez un constructeur:
Professeur p = new Professeur();
Lorsque vous instanciez un objet membre d'une sous-classe, vous appelez à la fois le constructeur de la superclasse et le constructeur de la sous-classe. Lorsque vous créez un objet de sous-classe, le constructeur de la superclasse doit d'abord s'exécuter, puis le constructeur de la sous-classe est exécuté.
Lorsqu'une superclasse contient un constructeur par défaut et que vous instanciez un objet de sous-classe, l'exécution du constructeur de la superclasse est généralement transparente, c'est-à-dire que rien n'attire l'attention sur le fait que le constructeur de la superclasse est en cours d'exécution à moins que le constructeur contient une action telle que l'affichage d'un message. Cependant, vous devez savoir que lorsque vous créez un objet enfant, les constructeurs mère et enfant s'exécutent.
Exemple 3 :
class Personne { String nom; String cin; public Personne() { System.out.println("je suis le constructeur de la classe mère"); } } public class Professeur extends Personne { int PPR; double salaire; public Professeur() { System.out.println("je suis le constructeur de la classe enfant"); } public static void main(String args[]) { Professeur p = new Professeur(); } }
je suis le constructeur de la classe enfant
Lorsque vous ne fournissez pas de constructeur pour une classe, Java vous fournit automatiquement un constructeur par défaut qui ne nécessite pas d'arguments. Lorsque vous écrivez votre propre constructeur, vous remplacez la version fournie automatiquement.
Selon vos besoins, un constructeur que vous créez pour une classe peut être un constructeur par défaut ou nécessitant des arguments. Lorsqu'une superclasse n'a que des constructeurs nécessitant des arguments, vous devez être certain que toutes les sous-classes fournissent au constructeur de la superclasse les arguments dont il a besoin.
Lorsqu'une superclasse a un constructeur par défaut, vous pouvez créer une sous-classe avec ou sans son propre constructeur. Cela est vrai que le constructeur de superclasse par défaut soit celui fourni automatiquement ou celui que vous avez écrit.
Conditions dans lesquelles un constructeur de sous-classe est requis
Si une superclasse contient: | Alors ses sous-classes: |
---|---|
Aucun constructeur écrit par le programmeur | Ne nécessite pas de constructeurs |
Un constructeur par défaut est écrit par le programmeur | Ne nécessite pas de constructeurs |
Seuls les constructeurs autres que ceux par défaut | Doit contenir un constructeur qui appelle le constructeur de la superclasse |
La syntaxe de l'instruction qui appelle un constructeur de superclasse à partir du constructeur de sous-classe est la suivante:
super(liste des paramètres)
Le mot-clé super fait toujours référence à la super-classe de la classe dans laquelle vous l'utilisez.
Exemple 4 :
class Personne { String nom; String cin; public Personne(String nom, String cin) { this.nom = nom; this.cin = cin; } } public class Professeur extends Personne { int PPR; double salaire; public Professeur(String nom, String cin, int ppr, double salaire) { super(nom, cin); this.PPR = ppr; this.salaire = salaire; } public static void main(String args[]) { Professeur p = new Professeur("Mostafa", "D4566", 3423, 7000.5); } }
Accéder aux méthodes de la superclasse
Une sous-classe peut contenir une méthode portant le même nom et les mêmes arguments (la même signature) qu'une méthode de sa classe mère. Lorsque cela se produit et que vous utilisez le nom de la méthode avec un objet de sous-classe, la méthode de sous-classe redéfinie la méthode superclass. Toutefois, si une méthode a été redéfinie mais que vous souhaitez utiliser la version superclasse dans la sous-classe, vous pouvez utiliser le mot-clé super pour accéder à la méthode de la classe mère.
Exemple 5 :
class Personne { String nom; 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 { int PPR; 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(); // afficher() de la classe mère System.out.println(" PPR : " + PPR + " - Salaire : " + salaire); } public static void main(String args[]) { Professeur p = new Professeur("Mostafa", "D4566", 3423, 7000.5); p.afficher(); } }
PPR : 3423 - Salaire : 7000.5
Protectation des données
Lorsqu'une classe sert de superclasse aux autres classes que vous créez, vos sous-classes héritent de toutes les méthodes et attributs de la superclasse. Les méthodes d’une sous-classe peuvent utiliser l’ensemble des attributs et méthodes appartenant à son classe mère, à une exception: les membres privés de la classe mère ne sont pas accessibles dans les méthodes d’une classe enfant.
Parfois, vous voulez que les méthodes de sous-classe puissent accéder directement à certains attributs, ces attributs ne peuvent pas être private. Cependant, vous ne voulez pas que d'autres classes, non-enfants, aient accès à ces attributs, donc ils ne peuvent pas être public. Pour résoudre ce problème, vous pouvez créer les attributs en utilisant le modificateur d'accès protected.
L'accès protected vous fournit un niveau de sécurité intermédiaire entre l'accès public et private. Si vous créez une méthode ou un attribut protected, il peut être utilisé dans sa propre classe ou dans toute classe enfant, mais il ne peut pas être utilisé par des classes «extérieures».
En d'autres termes, les membres protégés sont ceux qui peuvent être utilisés par une classe et ses descendants.
Vous devez protéger les champs de la classe mère uniquement si vous souhaitez que les classes enfant puissent accéder directement aux données de la classe mère, tout en interdisant à d'autres classes d'accéder aux champs.
Méthodes que vous ne pouvez pas les redéfinir
Parfois, lorsque vous créez une classe, vous pouvez choisir de ne pas autoriser les sous-classes à redéfinir certaines des méthodes de la superclasse. Par exemple, une classe Employe peut contenir une méthode qui calcule le numéro d’identification de chaque employé en fonction des attributs spécifiques au classe Employe. Par ailleurs, vous voudrez peut-être qu'aucune classe dérivée ne puisse fournir sa propre version de cette méthode.
Les trois types de méthodes que vous ne pouvez pas redéfinir dans une sous-classe sont:
- méthodes statiques
- méthodes finales
- Méthodes dans les classes finales