ENPC - Objets et Patterns - Séance 4

Héritage implicite et explicite

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().

Construction des instances de sous-classes

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.

Sous-typage et polymorphisme

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 Disque

Le 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

Masquage versus redéfinition

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?

Exercice

Rédigez les exercices 1 à 6 de cette feuille et envoyez les moi par mail avant le mardi 28 novembre.


Etienne.Duris[at]univ-mlv.fr - © École Nationale des Ponts et Chaussées - Novembre 2000 - http://www-igm.univ-mlv.fr/~duris/ENPC/index2000.html