Vous êtes sur la page 1sur 60

Ch. IV.

Héritage

Dr. T.HAJJI
Ch. IV. Héritage
L'héritage est une notion fondamentale en Java
et de manière générale dans les langages de
programmation par Objets.

Il permet de créer des classes dérivées (classes


qui héritent) par combinaison de classes déjà
existantes (classes de base) sans toucher au
code source de la classe de base (on a
seulement besoin du code compilé de la classe
de base).
La classe dérivée:
–se comporte comme la classe de base mais
avec quelques différences.
–on a seulement besoin du code compilé de
la clase de base.

On peut par exemple:


– ajouter de nouvelles méthodes
– adapter (modifier) certaines méthodes
Syntaxe:
class <ClasseDerivee> extends <ClasseDeBase>
Interprétation:
Permet de définir un lien d'héritage entre deux classes:
- La classe <ClasseDeBase> est le nom de la classe de base. Elle
s'appelle une classe mère, une classe parente ou une super-
classe. Le mot clef extends indique la classe mère :
- La classe <ClasseDerivee> est le nom de la classe dérivée. Elle
hérite de la classe <ClasseDeBase> . On l'appelle une classe fille
ou une sous-classe
Remarque:
- Par défaut il n’y a pas de extends dans la définition d’une classe:
une classe hérite de la classe Object
- Un objet de la classe dérivée ClassDerivee accède aux membres
publics de sa classe de base ClasseDeBase
Exemple
class FormeGeometrique{ // classe mère
// Définition des attributs et des méthodes
}
class Point extends FormeGeometrique { // classe dérivée
// Définition de nouveaux attributs (propres à la classe Point)
// Définition de nouvelles méthodes (propres à la classe Point)
// Modification des méthodes qui sont déjà définies dans la
classe mère
// Point hérite des méthodes et attributs de la superclasse
FormeGeometrique
}
class Cercle extends Point {
Définition de nouveaux attributs (propres à la classe Cercle)
// Définition de nouvelles méthodes (propres à la classe Cercle)
// Modification des méthodes qui sont déjà définies dans la
classe mère
// Cercle hérite des méthodes et attributs de la superclasse Point
}
Une instance (un objet) d’une classe dérivée peut
faire appelle (accès depuis l’extérieur)
- Aux méthodes et aux membres publics de la
classe dérivée
- Aux méthodes et membres publics de la classe
de base

De même (pour l’accès depuis l’intérieur)


- Une méthode d’une classe dérivée a accès aux
membres publics de sa classe de base
- Une méthode d’une classe dérivée n’a pas accès
aux membres privées de sa classe de base
Quand on écrit la classe <ClasseDerivee> on doit
seulement:
– écrire le code (variables ou méthodes) lié aux
nouvelles possibilités: offrir de nouveaux services.
– redéfinir certaines méthodes: enrichir les services
rendus par une classe.
Lorsqu'une classe hérite d'une autre classe
 Elle bénéficie automatiquement des
définitions des attributs et des méthodes de
la classe mère.
 Elle peut y ajouter ses propres définitions.
• ajouter des variables, des méthodes et des
constructeurs.
• redéfinir des méthodes: exactement le
même nom et la même signature.
• surcharger des méthodes: même nom mais
pas les mêmes arguments (signature).
Remarques2:
- Java ne permet pas l’héritage multiple: chaque
classe a une et une seule classe mère dont elle
hérite les variables et les méthodes.
- C++ permet l’héritage multiple.
Construction des classes dérivée
- Lorsqu'on construit une instance de ClasseDerivee, on
obtient un objet dont une partie est construite grâce à
la définition de ClasseDerivee et une partie grâce à la
définition de la super-classe ClasseDeBase.
- En Java, le constructeur de la classe drivée doit prendre
en
charge l’intégralité de la construction de l’objet.
 Le constructeur de la classe dérivée doit faire appel
au constructeur de la classe de base en appelant
explicitement la méthode super() ainsi que la liste
des paramètres appropriés.
Remarque: l’appel du constructeur de la super classe
(appel de la méthode super()) doit être la première
instruction dans la définition du constructeur de la
classe dérivée.
Exemple 1:
class ClasseDeBase{
public ClasseDeBase(int i) { // constructeur de la classe de base
System.out.println(" Classe de Base: " + i);
}
}
class ClasseDerivee1 extends ClasseDeBase {
public ClasseDerivee1(int i, int j) { // constructeur de la classe dérivée 1
super(i+j); // appel du constructeur ClasseDeBase
System.out.println(" Classe dérivée1: " + i+ " , "+j);
}
}
class ClasseDerivee2 extends ClasseDerivee1{
public ClasseDerivee2(int i) { // constructeur de la classe dérivée 2
super(i,i); // appel du constructeur ClasseDerivee1
System.out.println(" Classe dérivée2: " + i);
}
}
public class TestHeritage{
public static void main (String args[]) {
ClasseDerivee2 a=new ClasseDerivee2(7);
}
}

Sortie:
Classe de Base : 14
Classe dérivée1: 7 , 7
Classe dérivée2: 7
Exemples des cas possibles:
Exemple 1:
class ClasseDeBase{
ClasseDeBase(arguments) {

}

}
class ClasseDerivee extends ClasseDeBase {
// Pas de constructeur
….
}
On obtient une erreur de compilation. Un constructeur de la classe
dérivée doit être défini est qui doit appeler le constructeur de la
classe de base.
Exemple2:
class ClasseDeBase{
ClasseDeBase(arguments) { // constructeur de la classe de
base

}
}
class ClasseDerivee extends ClasseDeBase {
ClasseDerivee(arguments) { // constructeur de la classe de
base
// l’appel de super () est obligatoire sinon une erreur
….
}
}
Exemple 3:
class ClasseDeBase{
ClasseDeBase(arguments) { // constructeur de la classe de base

}
ClasseDeBase() { // constructeur sans paramètre de la classe de
base

}
}
class ClasseDerivee extends ClasseDeBase {
ClasseDerivee(arguments) {// constructeur de la classe
dérivée
// l’appel de super () n’est pas obligatoire.
// si super() n’est pas appelé explicitement,
// le constructeur par défaut de ClasseDeBase est appelé
….
}
}
Exemple 4:
class ClasseDeBase{
// Pas de constructeur

}
class ClasseDerivee extends ClasseDeBase {
// Pas de constructeur
….
}

La création d’un objet de type ClasseDerivee entraîne


l’appel de constructeur par défaut qui appelle le
constructeur par défaut de la ClasseDeBase.
Exemple pratique:
class ClasseDeBase{
ClasseDeBase() {
System.out.println(" Classe de Base: ");
}
}
class ClasseDerivee1 extends ClasseDeBase {
ClasseDerivee1() {
// Appel implicite du constructeur par défaut de ClasseDeBase
System.out.println(" Classe dérivée1: ");
}
}
class ClasseDerivee2 extends ClasseDerivee1{
ClasseDerivee2() {
// Appel implicite du constructeur par défaut de ClasseDerivee1
System.out.println(" Classe dérivée2: " );
}
}
public class TestHeritage{
public static void main (String args[]) {
ClasseDerivee2 a=new ClasseDerivee2();
}
}

Sortie:
Classe de Base :
Classe dérivée1:
Classe dérivée2:
Résumé:
Si on n'appelle pas le constructeur de la
super-classe, le constructeur par défaut est
utilisé si:
- aucun constructeur n’est défini
- au moins un constructeur sans paramètre
est défini
sinon le compilateur déclare une erreur
Notion de la Redéfinition

Ne pas confondre redéfinition et surcharge des


méthodes :
– on redéfinit une méthode quand une nouvelle
méthode a le même nom et la même
signature qu’une méthode héritée de la classe
mère.
– on surcharge une méthode quand une
nouvelle méthode a le même nom, mais pas la
même signature, qu’une autre méthode de la
même classe
- Lorsqu'un attribut ou une méthode ont été définis
dans une classe et sont redéfinis dans une classe
dérivée (qui en hérite), les éléments visibles sont
ceux redéfinis dans la classe dérivée. Les éléments de
la classe de base (héritée) sont alors masqués.

class Rect extends ObjetGraphique {


void move(int dx, int dy);
...
}
class Cercle extends ObjetGraphique{
void move(int dx, int dy);
...
}
- On peut avoir les mêmes méthodes move()
dans des classes héritant les unes des
autres. Dans ce cas, c'est la classe la plus
dérivée de l'objet qui détermine la
méthode à exécuter, sans que le
programmeur ait à faire des tests sur le
type de l'objet à traiter.
A* *: signifie la définition ou la
redéfinition d’une méthode
f.

Class A: méthode de A
B
C* Classe B: méthode de A
Classe D: méthode de D
Classe E: méthode de A
Classe C: méthode de C

E Classe F: méthode de C
D* F
- la redéfinition d'une méthode d'une classe consiste à
fournir dans une sous-classe une nouvelle
implémentation de la méthode. Cette nouvelle
implémentation masque alors complètement celle de la
super-classe

- Grâce au mot-clé super, la méthode redéfinie dans la


sous-classe peut réutiliser du code écrit dans la
méthode de la super-classe, qui n'est plus visible
autrement.

- super à un sens uniquement dans une méthode (comme


le mot-clé this).
class Point {
public void afficher(){
System.out.pritln(" je suis en "+ x +" et " +y);
}
private int x,y;
}
class PointCol extends Point {
public void afficher() { // redéfinition de la méthode afficher
super.afficher(); // appel de la méthode afficher de la classe
de base
System.out.println (" et ma couleur est: "+ couleur);
}
private byte couleur;
}
Soit une classe B qui hérite d'une classe A. Soit f une
méthode définie dans A. Dans une méthode
d'instance g() de B, super. sert à désigner un
membre de A.
par exemple: super.f() désigne la méthode f() de A

Attention: On ne peut remonter plus haut que la


classe mère pour récupérer une méthode
redéfinie :
– pas de « super.super.»
Exemple:
class A {
public void f(){
System.out.println("Je suis dans la classe de base A");
}
}
class B extends A {
public void g(){
super.f(); // désigne la méthode f() de la classe mère A
System.out.println("je suis aussi dans la class derivee B");
}
}
public class TestRedefinition {
public static void main (String args[]) {
B b = new B();
b.g();
}
}
Héritage (Suite)

• Une Classe final ne peut pas avoir de classes


filles
• La redéfinition d'une méthode public ne peut
être private
• Une méthode final ne peut pas être redéfinie
Sous-type
Le type B est un sous-type de A si on peut affecter une
expression de type B dans une variable de type A.
Les type primitifs
- int est un sous type de float
- float est un sous type de double
- …
Les objets
Les sous-classes d’une classe A sont des sous types de A.
Dans ce cas on peut écrire:
A a = new B(…); // la variable a est de type A, alors que
// l’objet référencé par a est de type
B.
A aa; B b=new B();
aa=b; // aa de type A, référence un objet de type B
Définitions:
- La classe (ou le type) réelle de l’objet est la classe du
constructeur qui a créé l’objet
Exemple: Soit B une sous classe de A
A a = new B(…); // B est la classe (type) réelle de
l’objet a

- Le type (la clase) déclaré de l’objet est le type qui est


donné au moment de la déclaration de la variable qui
référence l’objet.
Exemple: Soit B une sous classe de A
A a = new B(…); // A est le type déclaré de l’objet a
Cas des tableaux
Soit B une classe qui hérite de la classe A, alors on peut
écrire :
A[] tab = new B[5];
Attention: Dans tab[] il faut les valeurs de type réel et non
celles du type déclaré (c’est à dire des valeur de type B
et non de type A). Par exemple, si on a les instructions
suivantes:
A[] tab = new B[5];
A a = new A();
tab[0] = a;
• Passe à la compilation mais provoquera une
erreur à l’exécution car tab[0] reçoit une valeur
de type A et non une valeur de type B
Polymorphisme
Exemple introductif:
Considérons l’exemple suivant où B est une classe qui hérite de la classe A. Soient f() une
méthode qui reédinie dans B et g une méthode définie dans A. On suppose que

class A {
public void f(){
System.out.println("Methode f(): Classe de base A");
}
public void g(){
System.out.println("Methode g(): Classe de base A");
}
}
class B extends A {
public void f(){
System.out.println("Methode f(): Classe B derivee de A");
public void h(){
System.out.println("Methode h(): Classe B derivee de A");
}
}
A a=new A(); B b = new B();
a.f(); // appelle la méthode définie dans A
a=b; // A a = new(B);
a.f(); // appelle la méthode définie dans B
a.g(); // appelle la méthode définie dans A
b.g(); // appelle la méthode définie dans A

Il faut noter que la même écriture a.f(); peut correspondre à des


appels différents de la méthode f(). Ceci est réalisé grâce au
polymorphisme.

Le Polymorphisme veut dire que le même service peut avoir un


comportement différent suivant la classe dans laquelle il est
utilisé. C’est un concept fondamental de la programmation objet,
indispensable pour une utilisation efficace de l’héritage
Attention c’est important:
Si on a:
B b = new B();
b.h(); // appelle la méthode h() définie dans B
A a=new B();
a.h(); // erreur à la compilation même si la classe réelle
possède
// la méthode h(). En effet la classe déclarée
(classe A) // ne ossède pas la méthode h().

• En effet, en Java, dès la compilation on doit garantir l’existence de


la méthode appelée typage statique) : la classe déclarée de l’objet
qui reçoit le message (ici la classe A ou une de ces classes
ancêtres (polymorphisme)) doit posséder cette méthode ().
• Le polymorphisme est obtenu grâce au
mécanisme de la liaison retardée (tardive) «
late binding »: la méthode qui sera exécutée
est déterminée
– seulement à l’exécution, et pas dès la compilation
– par le type réel de l’objet qui reçoit le message (et
pas par son type déclaré)
Mécanisme de la liaison retardée: cas des méthodes redéfinies
Soit D la classe réelle d’un objet d (D est la classe du constructeur qui
a créé l’objet d) et soit A la classe de déclaration de l’objet d (A d =
new D();). Si on a l’instruction: d.f();
quelle méthode f() sera appelée ?
1. Si la méthode f() n’est pas définie dans une classe ancêtre de la
classe de déclaration (classe A) alors erreur de compilation.
2. Si non
– Si la méthode f() est redéfinie dans la classe D, alors c’est cette
méthode qui sera exécutée
– Sinon, la recherche de la méthode f() se poursuit dans la classe
mère de D, puis dans la classe mère de cette classe mère, et
ainsi de suite, jusqu’à trouver la définition d’une méthode f() qui
sera alors exécutée.
La méthode appelée ne dépend que du type réel de l’objet et non
du type déclaré.

Soit B une classe qui hérite de la classe A, et soit f() une méthode
définie dans A et redéfinie dans B. Si on a:
A a = new B();
a.f();
Alors la méthode f() appelée est celle définie dans B.

• Si C hérite de B et si la méthode f() est redéfinie dans C, alors si


on a:
A a = new C(); // Ok car C hérite de B qui hérite de A
a.f();
La méthode appelée est celle définie dans C.
Utilités du Polymorphisme:
Le polymorphisme permet d’éviter les codes qui
comportent de nombreux embranchements et tests.

Exemple
Considérons une classe FormeGeometrique.
Supposons que les classes Rectangle et Cercle
héritent de la super classe FormeGeometrique. Dans
un tableau hétérogène, on ranges des objets de type
Rectangle, Cercle. Ensuite on affiche le contenu du
tableau
Exemple
class FormeGeometrique {
public void dessineRectangle() {} // méthode vide
public void dessineCercle() {} // méthode vide
}
class Rectangle extends FormeGeometrique {
public void dessineRectangle() {
System.out.println("Je suis un rectangle ");
}
}
class Cercle extends FormeGeometrique {
public void dessineCercle() {
System.out.println("Je suis un cercle .... ");
}
}
public class TestFigPoly {
public static void main(String[] args) {
FormeGeometrique [] figures = new
FormeGeometrique[3];
figures[0]=new Rectangle();
figures[1]=new Cercle();
figures[2]=new Cercle();
for (int i=0; i < figures.length; i++) {
if (figures[i] instanceof Rectangle)
figures[i].dessineRectangle();
else if (figures[i] instanceof Cercle)
figures[i].dessineCercle();
}
}
}
instanceof: Soit b est une instance d'une classe B alors (b instanceof
A) retourne true si B est une sous classe de A. En particulier (b
instanceof B) retourne true.
Inconvénients dans le code précédent:
1. Si on veut rajouter une nouvelle forme
géométrique par exemple, triangle alors on est
obliger de:
- changer dans le code source aux niveaux définition de la
classe (rajouter la méthode vide public void
dessineTriangle() {})
- Rajouter un branchement pour la forme Triangle
2. Si on traite plusieurs formes géométrique, alors le
code comportera plusieurs tests.

En exploitant le polymorphisme, on peut améliorer


le code précédent
- Eviter les tests
- Rendre le code extensible: rajouter de
nouvelles sous-classes sans toucher au code
existant)
L’exemple peut être réécrit de la manière suivante en
exploitant le polymorphisme
class FormeGeometrique {
public void dessineFigure() {} // méthode vide
}
class Rectangle extends FormeGeometrique {
public void dessineFigure() {
System.out.println("Je suis un rectangle ");
}
}
class Cercle extends FormeGeometrique {
public void dessineFigure() {
System.out.println("Je suis un cercle .... ");
}
}
public class TestFigPoly {
public static void main(String[] args) {
FormeGeometrique [] figures = new
FormeGeometrique[3];
figures[0] = new Rectangle();
figures[1] = new Cercle();
figures[2] = new Cercle();
for (int i=0; i < figures.length; i++)
figures[i].dessineFigure();
// pas besoin de spécifier quelle forme
}
}
Un autre exemple d’exploitation du Polymorphisme:
Considérons l’exemple précédent, en supposant que le
rectangle et le cercle sont caractérisés par des champs
privés qui désignent:
- La position dans le plan (position du centre)
- La longueur et la largeur pour le rectangle
- Le rayon pour le Cercle.

Considérons un tableau hétérogène qui contient des


formes géométriques différentes: rectangles, cercles, ...

Le but et d’afficher le type de la forme géométrique et ses


caractéristiques
1ère solution:
class FormeFigure {
public void dessineFigure() {}
}

class Rectangle extends FormeFigure {


public Rectangle(int x, int y, int longueur, int largeur) { // centre (x,y)
this.x=x; this.y=y;
this.largeur=largeur; this.longueur=longueur;
}
public void dessineFigure() {
System.out.println(" Je suis un Rectangle de longueur " +longueur +" et de largeur " +
largeur);
System.out.println(" ma position dans le plan est (" + x + " ," + y + ")" );
}
private int x,y;
private int largeur, longueur;
}
class Cercle extends FormeFigure {
public Cercle(int x, int y, int rayon){ // centre (x,y)
this.x=x; this.y=y;
this.rayon=rayon;
}
public void dessineFigure(){
System.out.println(" Je suis un cercle de Rayon " + rayon);
System.out.println(" ma position dans le plan est (" + x + " ," + y + ")" );
}
private int x,y;
private int rayon;
}
public class TestFigPolySuite {
public static void main(String[] args) {
FormeFigure [] figures = new FormeFigure[2];
figures[0]=new Rectangle(1,2,15,6);
figures[1]=new Cercle(0,0,4);
for (int i=0; i < figures.length; i++)
figures[i].dessineFigure();
}
}
2ème solution : on ne redéfinie pas la position dans le plan qui est la
même pour toutes les formes géométrique.

class FormeFigure {
public FormeFigure(int x, int y) { // constructeur
this.x=x; this.y=y;
}
public void dessineFigure() {
afficheMonIdentite();
System.out.println(" ma position dans le plan est (" + x + " ," + y +
")" );
}
public void afficheMonIdentite(){}; // méthode vide
private int x,y;
}
class Rectangle extends FormeFigure {
public Rectangle(int x, int y, int longueur, int largeur) { // centre (x,y)
super(x,y);
this.largeur=largeur; this.longueur=longueur;
}
public void afficheMonIdentite() {
System.out.println(" Je suis un Rectangle de longueur " +longueur +" et de largeur " +
largeur);
}
private int largeur, int longueur;
}
class Cercle extends FormeFigure {
public Cercle(int x, int y, int rayon){ // centre (x,y)
super(x,y);
this.rayon=rayon;
}
public void afficheMonIdentite() {
System.out.println(" Je suis un cercle de Rayon " + rayon);
}
private int rayon;
}
public class TestFigPolySuite {
public static void main(String[] args) {
FormeFigure [] figures = new FormeFigure[2];
figures[0]=new Rectangle(1,2,15,6);
figures[1]=new Cercle(0,0,4);
for (int i=0; i < figures.length; i++)
figures[i].dessineFigure();
// la méthode afficheMonIdentite() appelée est celle définie dans la
classe
//réelle de l’objet et non celle définie dans la classe de déclaration
}
}
Cast (transtypage): conversions de classes
• Le « cast » est le fait de forcer le compilateur à
considérer un objet comme étant d’un type qui
n’est pas le type déclaré ou réel de l’objet

• En Java, les seuls casts autorisés entre classes


sont les casts entre classe mère et classes filles
Syntaxe
• Pour caster un objet en classe A :
(C) o;

• Exemple :
FormeFigure a = new FormeFigure();
Rectangle r = (Rectangle) a;
FormeFigure b;
(Rectangle) b = r;
UpCast : classe fille → classe mère

• Upcast : un objet est considéré comme une


instance d’une des classes ancêtres de sa classe
réelle
• Il est toujours possible de faire un upcast car tout
objet peut être considéré comme une instance
d’une classe ancêtre
• Le upcast est souvent implicite
DownCast : classe mère → classe fille

• Downcast : un objet est considéré comme étant


d’une classe fille de sa classe de déclaration
• Toujours accepté par le compilateur
• Mais peut provoquer une erreur à l’exécution ; à
l’exécution il sera vérifié que l’objet est bien de la
classe fille
• Un downcast doit toujours être explicite
Classes abstraites

Une classe abstraite (abstract class) est une classe


déclarée avec le mot clé abstract class.
Par exemple:
abstract class A {

}

Dans une classe abstraite on peut trouver: des champs,


des méthodes et des méthodes abstraites
• Une méthode est abstraite, lorsqu’on la déclare
sans donner son implémentation: on ne fournit
que le type de la valeur de retour et la signature
(l’entête de la méthode).
• Les méthodes abstraite sont déclarée avec le
mot clè abstract.
• Exemple:
abstract void f(int i, float x);
abstract double g();

• La méthode peut être implémentée par les


classes filles.
- On peut créer une référence sur un objet de type A
(une classe abstraite) mais il est interdit de créer une
instance (un objet) d’une classe abstraite
A a; // autorisé
A a = new A(); // n’est pas autorisé.
- Si B hérite de A alors on peut écrire:
A a = new B();
- Une méthode static ne peut être abstraite (car on ne
peut redéfinir une méthode static)
- Une classe qui contient au moins une méthode
abstraite est automatiquement abstraite
- Une méthode déclarée abstraite doit obligatoirement
être déclarée public.
Intérêt des classe abstraites:

– la classe mère définit la structure globale d’un


algorithme
– elle laisse aux classes filles le soin de définir des
points bien précis de l’algorithme.
Interfaces

Définition des interfaces


• Une classe est purement abstraite si toutes ses
méthodes sont abstraites.
• Une interface est une « classe » purement
abstraite déclarée avec le mot clé interface, dont
toutes les méthodes sont publiques.

• Une interface est une liste de noms de méthodes


publiques.
Exemples d’interfaces
public interface Figure {
public abstract void dessineFigure();
public abstract void deplaceFigure(int dx, int
dy);
}

Les interfaces sont implémentées par des classes


• Une classe implémente une interface I si elle déclare «
implements I » dans son en-tête

Vous aimerez peut-être aussi