Toute classe en java hérite implicitement de la classe Object. Il est possible de demander à ce qu'une classe, dite de base, hérite (ou étende) explicitement d'une autre, dite classe dérivée. On utilise pour cela le mot-clé extends de la manière suivante:
class Base { int i; void m1() { ... } ... } class Derivee extends Base { int j; void m2() { ... } ... }
On dit alors que la classe Derivee est une sous-classe de la classe Base.
La classe Derivee hérite de la classe Base tous ses membres:
Souvent, la relation qui unie une classe dérivée à une classe de base est de type spécialisation. Par exemple, si l'on considère qu'un étudiant est une personne, on pourra définir:
class Personne { String nom; int insee; Personne(String nom, int insee) { this.nom = nom; this.insee = insee; } ... String getAnneeDeNaissance() { ... } } class Etudiant extends Personne { String discipline; int annee; ... int getAnneesAvantDiplome() { ... } }
Si e est une instance d'Etudiant, il est tout
à fait possible de consulter e.nom et d'appeler
e.getAnneeDeNaissance.
Ce qu'il est bon de se rappeler,
c'est que ce qu'on peut faire sur une instance d'une super classe,
on doit pouvoir le faire sur toute instance d'une sous-classe, mais
pas le contraire ou encore que une classe dérivée
a toutes les fonctionnalités de la classe dont elle
hérite, plus ses fonctionnalités propres.
Exercice 1: On considère qu'un anneau est un
disque plus la donnée d'un rayon interne. Écrire une
classe Anneau qui hérite de la classe
Disque.
Exercice 2: Créez dans la classe
Disque une méthode diametre() et dans la
classe Anneau une méthode
diametreInterne().
Lors de la création d'une instance d'une sous-classe, la première chose que fait la machine virtuelle est de créer une instance de la super-classe, et ce récursivement jusqu'à créer une instance de la classe Object, mère de toute les classes. Tout se passe en fait comme si le constructeur de la sous-classe contenait un appel explicite au constructeur de la super-classe par super():
class Derivee extends Base { Derivee() { super(); // appel au constructeur sans argument de la super-classe } }
Tout comme avec l'appel this() qui fait appel à un autre constructeur de la même classe, il est possible de faire appel à un constructeur particulier de la super-classe par un appel à super() avec les arguments correspondants. Exemple:
class Etudiant extends Personne { String discipline; int annee; Etudiant(String nom, int insee, String discipline, int annee) { super(nom, insee); // appel explicite au constructeur de Personne this.discipline = discipline; this.annee = annee; } ... int getAnneesAvantDiplome() { ... } }
Attention: s'il y a un appel explicite à super() dans un constructeur, celui-ci doit obligatoirement être la première instruction.
Exercice 3: Peut-on disposer d'un constructeur par
défaut dans la classe Anneau s'il n'y a pas de
constructeur sans argument dans Disque? Pourquoi?
Exercice 4: Résolvez le problème en faisant en
sorte qu'un appel à new Anneau() construise un anneau
ayant les caractéristiques d'un disque par défaut et de
rayon interne nul.
De plus, l'héritage fournit une notion de type fondamentale
en programmation objet. Nous avons déjà vu que toute
classe C définit un type correspondant de même
nom, C. Une sous-classe définit un sous-type du type
défini par la classe dont elle hérite. Ainsi, dans notre
précédent exemple, le type Anneau est un
sous-type du type Disque.
Puisque nous avons vu que toute classe est, directement ou
indirectement, une sous-classe implicite de la classe Object,
cela signifie que toute classe définit un sous-type du type
Object. Ainsi, Anneau est sous-type du type
Disque, lui-même sous-type du type Object.
Cette notion de type et de sous typage a plusieurs utilités parmi lesquelles:
Anneau a1 = new Anneau(); // une instance d'Anneau peut être stockée Disque d = a1; // dans une variable déclarée de type DisqueLe contraire est naturellement interdit de manière implicite, et nécessite un transtypage (ou cast):
Anneau a2 = d; // Erreur: cast explicite obligatoire Anneau a2 = (Anneau) d; // Correct si d contient réellement une référence // à une instance d'Anneau
Les champs qui sont hérités de la classe de base dans la classe dérivée peuvent être masqués par la définition dans la classe dérivée d'un champ du même nom. Dans ce cas, le champ de la super-classe est accessible grâce à l'instruction super.
Exercice 5: Définissez un champ couleur de type String dans la classe Disque. Rédéfinissez (masquez) ce champ par un champ couleur de type int dans la classe Anneau. Testez en différents points du code quelle est le champ couleur qui est visible (String ou int). Comment faire, à partir d'une instance de la classe Anneau, pour récupérer la valeur du champ couleur de type String.
Exercice 6: Que pensez vous de la méthode méthode surface() accessible par héritage sur les instances de la classe Anneau? Redéfinissez-la pour qu'elle retourne une valeur correcte pour un anneau. Comment faire alors, à partir d'une instance de la classe Anneau, pour accéder à la méthode surface() de la classe Disque?