Héritage et polymorphisme
Cours · programmation Java (école d'ingénieur), chapitre 9 · école d'ingénieur, informatique · le début du chapitre, la suite sur Adloun
Un logiciel de dessin manipule des cercles, des rectangles, des triangles. Tous ont un nom, tous savent calculer leur aire et se décrire ; mais chacun calcule son aire à sa façon. Écrire trois classes indépendantes, c'est recopier trois fois le même code pour le nom et la description, et ne pas pouvoir ranger ces formes dans un même tableau pour, par exemple, calculer l'aire totale d'un dessin.
L'héritage répond à ces deux besoins. Il permet de définir une classe Cercle à partir d'une classe plus générale Forme : le cercle reçoit tout ce que sait faire une forme, et n'écrit que ce qui lui est propre. Le polymorphisme, ensuite, permet de traiter un cercle ou un rectangle simplement comme « une forme » : quand on demande son aire à une forme, c'est la méthode du cercle ou celle du rectangle qui s'exécute, selon l'objet réellement rangé dans la variable.
Objectifs du chapitre
- Définir une sous-classe avec
extends, appeler le constructeur de la super-classe avecsuper(...). - Redéfinir une méthode (
@Override) et ne pas la confondre avec une surcharge. - Distinguer type statique et type dynamique ; comprendre la liaison dynamique, qui fait le polymorphisme.
- Redéfinir correctement
toString,equalsethashCode, hérités deObject. - Tester le type d'un objet avec
instanceofet savoir quand ne pas hériter.
9.1 Définir une sous-classe
9.1.1 Le mot-clé extends
Une classe B hérite d'une classe A quand elle est déclarée par class B extends A. On dit que B est une sous-classe (ou classe dérivée, ou classe fille) de A, et que A est la super-classe (ou classe mère) de B. Un objet de type B possède tous les attributs et toutes les méthodes de A, plus ceux que B ajoute.
En Java, une classe n'hérite que d'une seule classe : c'est l'héritage simple. En revanche, une classe peut avoir autant de sous-classes que l'on veut, et une sous-classe peut à son tour avoir des sous-classes : on obtient une hiérarchie de classes.
L'héritage doit traduire la relation « est un » : un cercle est une forme, un cadre est un employé, une voiture est un véhicule. Si la phrase sonne faux, l'héritage est sans doute une erreur (section 9.7).
Voici la hiérarchie de notre logiciel de dessin. La classe Forme contient ce qui est commun ; les sous-classes Cercle et Rectangle ajoutent leurs dimensions et leur propre calcul d'aire.
class Forme {
protected String nom;
Forme(String nom) {
this.nom = nom;
}
double aire() {
return 0.0; // une forme « quelconque » n'a pas d'aire connue
}
String decrire() {
return nom + " : aire " + String.format("%.3f", aire());
}
}
class Cercle extends Forme {
private double rayon;
Cercle(String nom, double rayon) {
super(nom); // appelle le constructeur de Forme
this.rayon = rayon;
}
@Override
double aire() {
return Math.PI * rayon * rayon;
}
@Override
String decrire() {
return "Cercle " + super.decrire() + ", rayon " + rayon;
}
}
class Rectangle extends Forme {
private double largeur, hauteur;
Rectangle(String nom, double largeur, double hauteur) {
super(nom);
this.largeur = largeur;
this.hauteur = hauteur;
}
@Override
double aire() {
return largeur * hauteur;
}
}
On représente une telle hiérarchie par un diagramme de classes : une boîte par classe (son nom, ses attributs, ses méthodes), et une flèche à pointe creuse de la sous-classe vers la super-classe.
Le signe # note un membre protected, le signe - un membre private. Une sous-classe ne fait figurer que ce qu'elle ajoute ou redéfinit : le Cercle a bien un nom, mais il l'hérite.
9.1.2 Les constructeurs et super(...)
Les constructeurs ne s'héritent pas. Un objet Cercle contient pourtant un attribut nom, déclaré dans Forme : c'est au constructeur de Forme de l'initialiser. Le constructeur de Cercle l'appelle par l'instruction super(nom).
La première instruction d'un constructeur de sous-classe est l'appel super(...) à un constructeur de la super-classe. Si on ne l'écrit pas, le compilateur insère super();, c'est-à-dire un appel au constructeur sans paramètre de la super-classe.
La conséquence pratique est importante : si la super-classe n'a pas de constructeur sans paramètre, l'appel explicite à super(...) est obligatoire. Oublions-le dans une classe Carre :
class Carre extends Forme {
private double cote;
Carre(double cote) {
this.cote = cote;
}
}
Erreur1.java:12: error: constructor Forme in class Forme cannot be applied to given types;
Carre(double cote) {
^
required: String
found: no arguments
reason: actual and formal argument lists differ in length
1 error
Le message se lit ainsi : le compilateur a voulu appeler le constructeur de Forme « sans arguments » (found: no arguments), à cause du super(); implicite, mais ce constructeur attend une String (required: String). Il faut écrire super("carré"); en première ligne.
Trois classes en chaîne, C extends B et B extends A, dont les constructeurs affichent un message :
class A {
A() { System.out.println("constructeur de A"); }
}
class B extends A {
B() {
// super(); est inséré ici par le compilateur
System.out.println("constructeur de B");
}
}
class C extends B {
C() { System.out.println("constructeur de C"); }
}
L'instruction new C(); affiche :
constructeur de A
constructeur de B
constructeur de C
Le constructeur de C commence par appeler celui de B, qui commence par appeler celui de A. Un objet se construit donc de la racine vers les feuilles : la partie « A » de l'objet est prête avant la partie « B », qui est prête avant la partie « C ».
9.1.3 Ce qu'une sous-classe voit : protected
Un attribut private de la super-classe existe bien dans l'objet de la sous-classe, mais le code de la sous-classe n'y a pas accès :
class Compte {
private double solde;
}
class CompteEpargne extends Compte {
void ajouterInterets(double taux) {
solde = solde * (1 + taux);
}
}
Erreur3.java:11: error: solde has private access in Compte
solde = solde * (1 + taux);
^
Deux solutions. La première, souvent la meilleure, consiste à passer par les méthodes publiques de la super-classe (getSolde(), crediter(...)) : la super-classe garde le contrôle de son invariant. La seconde consiste à déclarer l'attribut protected.
protectedUn membre protected est accessible dans sa classe, dans les sous-classes (même situées dans un autre paquet) et dans les classes de son paquet. Il est donc moins protégé qu'un membre private, plus qu'un membre public.
| modificateur | la classe | le paquet | les sous-classes | partout |
|---|---|---|---|---|
private | oui | non | non | non |
| (aucun) | oui | oui | non | non |
protected | oui | oui | oui | non |
public | oui | oui | oui | oui |
sauf, bien sûr, les sous-classes du même paquet.
La suite du cours est sur Adloun
Le chapitre en entier, formules et figures comprises, avec le tuteur, les fiches, les QCM et les exercices.
Lire la suite du coursEn accès libre, sans inscription.