next up previous contents index
suivant: 4.4 Extension et héritage monter: 4. Composition, abstraction, extension précédent: 4.2 Application : types   Table des matières   Index


4.3 Abstraction et sous-typage

Les points, les cercles et les rectangles peuvent être translatés, ils ont tous une surface, mais les instructions à exécuter pour les translater ou pour calculer leur surface dépend de la classe de l'objet considéré.

package geometrie;

class TestSurface {
  public static void main(String[] args) {
    Point o = new Point(), 
          p = new Point(1,1);
    Cercle c = new Cercle(o, 2);              
    Rectangle r = new Rectangle(o, p);       
    double sp = p.surface();
    double sc = c.surface();
    double sr = r.surface();
  }
}

Supposons maintenant que l'on veuille tirer un objet de l'un de ces types au hasard, à pile ou face, puis calculer sa surface. Il semble naturel de former l'expression suivante :

  (Math.random()>0.5 ? c : r).surface();

Cependant l'expression Math.random()>0.5 ? c : r n'est pas typée correctement parce que c et r ne sont pas du même type et aucun n'est un sous-type de l'autre. Il en est de même de l'expression Math.random()>0.5 ? true : 3, car boolean n'est pas un sous-type de int et vice-versa. La solution est d'introduire un nouveau type, Forme, dont Cercle et Rectangle seront des sous-types, de sorte que Math.random()>0.5 ? c : r soit une expression de type Forme. Le type Forme n'est pas une classe, mais une « interface » ; il ne définit pas la méthode surface, mais il la déclare, puisque toute forme a une surface, mais que le calcul de la surface ne peut être fait que pour une forme particulière. La méthode surface de l'interface Forme ne peut pas avoir de corps ; on dit que c'est une méthode abstraite et on la déclare en remplaçant le corps par un «;».

interface Forme {
  double surface();
}

Java offre en effet à côté des classes une autre catégorie de types, les interfaces. Rappelons que les classes de Java ont trois rôles :

  1. de typage, au sens usuel, ce qui permet de déclarer des noms d'un certain type et de vérifier que les expressions où ils apparaissent sont correctement typées ;
  2. de définition du comportement des objets, en définissant des méthodes ;
  3. de moule, pour construire leurs instances.
Les interfaces ne remplissent que le premier de ces trois rôles : ce sont des types abstraits au sens où elles ne définissent aucune méthode et ne comportent pas de constructeur : une interface déclare uniquement des méthodes publiques, le corps de la méthode étant remplacé par un « ; ». On ne peut pas créer un objet d'un type abstrait : une interface n'a pas d'instance, l'expression new Forme() n'est pas correcte.

Pour faire de Point, Cercle et Rectangle des sous-types de Forme, il suffit de déclarer qu'ils réalisent (ou implémentent) l'interface Forme, au moyen du mot-clé implements :

class Cercle implements Forme { ... }
class Point implements Forme { ... }
class Rectangle implements Forme { ... }

Le mot-clé implements peut être suivi de plusieurs noms d'interfaces ; la classe est alors considérée comme un sous-type de chacune de ces interfaces.

Déclarer qu'une classe réalise une (ou plusieurs) interface(s) comporte une obligation : il faut alors que la classe définisse chacune des méthodes déclarées dans cette (ou ces) interface(s), ou bien en hérite, nécessairement comme des méthodes publiques.

class Cercle implements Forme { 
  Point centre;
  double rayon;
  Cercle(Point centre, double rayon) {
    this.centre = centre;
    this.rayon = rayon;
  }
  public double surface() {
    return Math.PI * rayon * rayon;
  }
}

Si une classe, déclarée comme réalisation d'une interface, ne définit pas une méthode déclarée par cette interface, ou n'hérite pas d'un telle méthode, cette classe est considérée comme abstraite. Le compilateur émet alors un message d'erreur.

L'expression Math.random()>0.5 ? c : r est alors de type Forme et on peut lui appliquer la méthode surface, qui est abstraite dans Forme, mais définie dans ses sous-types Point, Cercle et Rectangle. On dit parfois que ces trois types sont des types concrets pour les distinguer des types abstraits. Ceci permet d'affecter à une variable de type Forme un objet de type Cercle :

Forme c = new Cercle(new Point(), 2);

Ceci permet aussi de placer dans un tableau de Forme des objets de types différents :

Forme[] t = new Forme[] {o, c};

Par contre, il n'est pas possible d'affecter à une variable de type $t$ une expression d'un sur-type de $t$, ou d'invoquer une méthode ou un constructeur avec un argument dont le type est un sur-type du type du paramètre correspondant. Par exemple, l'affectation suivante est refusée par le compilateur si c est de type Forme :

Point q = c;         // ERREUR à la compilation

Une opération de transtypage est indispensable dans ce cas et conduira à une vérification lors de l'exécution du fait si c désigne effectivement un objet de classe Point :

Point q = (Point)c; // correct à la compilation
                    // ERREUR à l'exécution : c n'est pas un Point

Une expression comportant un transtypage vers un sous-type est toujours correctement typée, mais son exécution pourra déclencher une exception à la compilation.


next up previous contents index
suivant: 4.4 Extension et héritage monter: 4. Composition, abstraction, extension précédent: 4.2 Application : types   Table des matières   Index
Rene' LALEMENT 2001-11-07