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 :
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
une expression d'un sur-type de
, 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.