Page précédenteIndexPage suivante

CHAPITRE 6 - L'héritage

Ici seront présentés les notions d'héritage, de classe abstraite, d'interface et l'ensemble des phénomènes touchant à l'héritage qu'il est nécessaire de maîtriser. A ce point, il est vivement conseillé de se rafraîchir la mémoire sur les concepts d'héritage du cours 1. Nous supposerons ces notions acquises.

6.1- L'héritage simple

Définition

L'héritage permet de spécialiser une classe qui possédera non seulement les propriétés et méthodes de son unique mère mais d'autres méthodes spécifiques ou redéfinies. Dans l'objet fille, on trouve:

 Pour spécifier qu'une classe dérive d'une autre classe, on utilise l'opérateur extends lors de la déclaration de la classe:

class Voiture extends Vehicule{
}

La classe mère commune à toutes les autres classes est la classe Object. Tous les objets dérivent implicitement d'Object et les déclarations suivantes sont identiques:

class Voiture{
}

class Voiture extends Object{
}


Types et affectation

Si mere est de type 'Mere' et fille de type 'Fille ' et que la classe Fille dérive de la classe Mere,

Fille fille=new Fille();
Mere mere=new Mere();

faire

mere=fille;

signifie que la référence mere est maintenant une référence sur objet de type Fille et plus sur Mere. Cette opération est acceptée car l'objet fille était de type Mere par héritage.

mere instanceof Fille;   renverra vrai.

En conséquence, mere peut maintenant appeler des méthodes de la classe Fille. A contrario, il est impossible de faire :

fille=mere;

Ceci renverra une erreur de compilation car un type Mere n'est pas un type Fille : un objet de type Fille contient des méthodes et des attributs qu'il n'y a pas dans la classe Mere.

Classes anonymes

Depuis le JDK 1.1, il est possible d'instancier un objet en effectuant une surcharge des méthode de sa classe. Cette modification n'est valable que pour l'instance surchargée. Exemple:

String s=new String(){
    public String toString(){
       return "TEST";
    }
};

Cela signifie que la méthode toString() de cette  chaîne de caractères renverra toujours"TEST". La surcharge d'instance peut être utile pour éviter de créer une nouvelle classe dérivant de l'objet à surcharger. Le compilateur générera alors une classe de nom <nom de la classe courante >~<no de surcharge>.class comme "Test~1.class". L'objet s de notre exemple est alors de type Test~1.


Transtypage implicite

Si une classe Fille dérive d'une classe Mere, une instance de la classe Fille sera de type Fille bien entendu mais sera également de type Mere par héritage.
 

Compléments sur le transtypage

Le transtypage d'un objet d'un type vers un autre type permet de modifier la façon dont on "voit" l'objet mais ne le modifie pas. Pour transtyper un objet vers un autre type, la notation est la même que pour le cast primitif:   (MaClasseDestination)monObjet
Il est possible de transtyper un objet vers un type ascendant  ( le type de sa classe mère ou la mère de sa mère etc...) même si ce transtypage est fait de façon implicite. En effet, un objet Voiture par exemple est implicitement un Vehicule - nul besoin de le transtyper en Vehicule pour accéder à ses propriétés et méthodes.

Dans certains cas cependant, il peut se trouver que les méthodes ou les propriétés portent le même nom dans la classe et dans ses classes ascendantes. Il existe alors des règles précises. Observons l'exemple suivant:

class Vehicule{
    int iAge=10;
    int getAge(){
        return iAge;
    }
}

class Voiture extends Vehicule{
    int iAge=20;
    int getAge(){
       return iAge;
    }
}

Instancions un objet de type Voiture:    Voiture v1=new Voiture();

Il est possible de transtyper v1 en Vehicule pour accéder à la propriété iAge de la classe Vehicule et non celle de Voiture:

((Vehicule)v1).iAge    pointe vers le iAge de Voiture.

A contrario, il n'est pas possible d'accéder à la méthode getAge() de la classe Vehicule à partir de v1:

((Vehicule)v1).getAge()   pointe vers la méthode getAge()  de la classe Voiture et non de la classe Vehicule malgré le cast.

Ainsi, le transtypage permet d'accéder aux propriétés de la classe vers laquelle nous transtypons l'objet mais les méthodes utilisées seront toujours les plus spécialisées, quoiqu'il arrive.

Il est également possible de transtyper un objet d'un type vers un type plus spécialisé mais seulement dans certaines conditions:

Vehicule monVehicule=new Vehicule();
Vehicule monVehicule2=(Voiture)monVehicule;   renverra une exception

mais il est possible de faire:

Voiture v1=new Voiture();
Vehicule monVehicule=(Vehicule)v1;   //v1 est vu comme un véhicule
Voiture v2=(Voiture)monVehicule;   //on cast vers un type plus spécialisé mais cela fonctionne car monVehicule était de type Voiture.  v1 et v2 référençient le même objet



6.2- Notion de classe abstraite

Dans certains cas, nous pouvons désirer implémenter deux objets proches possédant une partie de méthodes et de propriétés en commun comme Voiture et Moto. Certaines de ces méthodes comme rouler() sont totalement identiques d'un objet à l'autre alors que certaines autres diffèrent comme la méthode seGarer() qui est différente pour une Voiture et pour une Moto. Pour ce faire, l'OO introduit la notion de classe abstraite.

Définition

Une classe abstraite est une classe non-instanciable dont une partie des méthodes sont abstraites et restent à implémenter.

Remarques:

Notation d'une classe abstraite

abstract class MaClasse{

}

et pour une méthode

abstract void maMethode(); // jamais d'implémentation

Une méthode abstraite est une simple déclaration sans implémentation. Elle est simplement la marque que cette méthode devra être implémentée par la classe dérivée. Lorsqu'une classe dérive d'une classe abstraite, elle doit alors implémenter toutes les méthodes abstraites de celle-ci ou être elle-même abstraite. Cela permet lors d'un projet d'éviter d'"oublier" d'implémenter des méthodes communes.

Exemple

Dans la classe Véhicule, nous déclarons la méthode seGarer() en méthode abstraite. Alors, si Voiture dérive de Véhicule , la classe Voiture doit implémenter la méthode seGarer() pour décrire sa façon de se garer. La classe Moto doit implémenter la méthode seGarer() pour décrire la sienne.



6.3- Notion d'héritage multiple via interface

Définition

Certains langages orientés objet comme le C++ disposent de l'héritage multiple: une classe peut dériver de plusieurs classes. Ce concept peut amener à des incohérences dans certains cas. D'autres langages comme Java ou SmallTalk n'acceptent que l'héritage simple. Cependant, il peut être intéressant dans certains cas d'hériter de méthodes ou de propriétés communes à deux classes mères ou à leur ascendance. Pour contourner ce problème, nous disposons des interfaces. Une interface est une sorte de classe de déclaration dans laquelle  les méthodes sont seulement déclarées et les constantes fixées. Cependant, dans une interface, il n'y a aucune implémentation. Toute classe ne peut dériver que d'une classe mère mais implémenter une ou plusieurs interfaces. Si elle implémente une interface, elle doit implémenter toutes les méthodes de l'interface ou alors être abstraite.

Composition d'une interface

Une interface peut contenir:

Création d'une interface

interface PropriétéPrivée{
   boolean maMethode(int i);
   int autreMethode(float f);
   static final int i=10;
}


Implémentation d'une interface

class Voiture implements PropriétéPrivée{
   boolean maMethode(int i){
       .....
   }
   int autreMethode(float f){
       ...
   }
}

L'héritage chez les interfaces

Une interface peut dériver d'une autre interface et même de plusieurs. Dans ce cas, la première classe à implémenter une interface devra également implémenter les méthodes des mères de l'interface.


Conséquences sur le type de l'objet

Du point de vue du type, un objet est à la fois un objet du type de sa mère mais également des types de ses interfaces. Si l'interface et la classe mère possèdent des méthodes ou propriétés de même nom, il faut transtyper l'objet pour préciser quel type nous allons utiliser; par exemple si on utilise une instance de Voiture en tant que Vehicule ou en tant que PropriétéPrivée .



TD 6 - L'héritage

Exercice 1 - Classes abtraites

La classe Vector ( package java.util ) est couramment utilisée. Nous allons ici l'utiliser de deux façons pour créer une FIFO (First In / First Out) et une LIFO (Last In / First Out).

Object get();
void put(Object);

Exercice 2 - Interfaces



solutions





Page précédenteIndexPage suivante