• Aucun résultat trouvé

}

En fait, l’h´eritage des classes n’a que peu d’int´erˆet en pratique, l’h´eritage des objets est bien plus utile.

2 Obscur objet

Dans cette section, nous examinons la dialectique question de la classe et de l’objet.

2.1 Utilisation des objets

Sans mˆeme construire des objets nous mˆemes, nous en utilisons forc´ement, car l’environne-ment d’ex´ecution de Java est principalel’environne-ment en style objet. Autrel’environne-ment dit on sait d´ej`a que les objets ont des m´ethodes.

(1) Les tableaux sont presque des objets.

(2) Les chaˆınesStringsont des objets.

(3) La biblioth`eque construit des objets dont nous appelons les m´ethodes, voirout.println().

Les objets poss`edent aussi deschamps´egalement appel´esvariables d’instance, auxquels on acc`ede par la notation en (( . )) comme pour les variables de classes. Par exemple, si t est un tableau t.lengthest la taille du tableau.

La biblioth`eque de Java emploie ´enorm´ement les objets. Par exemple, le point est un objet de la classePointdu packagejava.awt. On cr´ee un nouveau point par un appel de constructeur dont la syntaxe est :

Point p = new Point () ; // Point origine

On peut aussi cr´eer un point en donnant explicitement ses coordonn´ees (enti`eres) au construc-teur :

Point p = new Point (100,100) ;

On dit que le constructeur de la classe Point est surcharg´e (overloaded), c’est-`a-dire qu’il y a en fait deux constructeurs qui se distinguent par le type de leurs arguments. Les m´ethodes, statiques ou non, peuvent ´egalement ˆetre surcharg´ees.

On peut ensuite acc´eder aux champs d’un point `a l’aide de la notation usuelle. Les points ont deux champs xetyqui sont leurs coordonn´ees horizontales et verticales :

i f (p.x == p.y)

System.out.println("Le point est sur la diagonale");

Les points poss`edent aussi des m´ethodes, par exemple la m´ethode distance, qui calcule la distance euclidienne entre deux points, renvoy´ee comme un flottant double pr´ecision double.

Un moyen assez compliqu´e d’afficher une approximation de√

2 est donc System.out.println(new Point ().distance(new Point (1, 1))) ;

Les objets sont reli´es aux classes de deux fa¸cons :

ˆ L’objet est cr´e´e par un constructeur d´efini dans une classe. Ce constructeur d´efinit la classe d’origine de l’objet, et l’objet garde cette classe-l`a toute sa vie.

ˆ Les classes sont aussi plus ou moins les types des objets, quand nous ´ecrivons Point p = . . .

Nous d´eclarons une variable de type Point. Dans certaines conditions (voir les sections 2.3 et III.1.2), la classe-type peut changer au cours de la vie l’objet. Le plus souvent, cela signifie qu’un objet dont la classe reste immuable, peut, dans certaines conditions, ˆetre rang´e dans une variable dont le type est une autre classe.

Un premier exemple (assez extrˆeme) de cette distinction est donn´e par null. La valeur null n’a ni classe d’origine (elle n’est pas cr´e´e par new), ni champs, ni m´ethodes, ni rien, mais alors rien du tout. En revanche, null appartient `a toutes les classes-types.

2.2 Fabrication des objets

Notre id´ee est de montrer comment cr´eer des objets tr`es similaires aux points de la section pr´ec´edente. On cr´ee tr`es facilement une classe des paires (d’entiers) de la fa¸con suivante : class Pair {

int x ; int y ;

Pair () { this(0,0) ; }

Pair (int x, int y) { this.x = x ; this.y = y ; } double distance(Pair p) {

int dx = p.x - this.x ; int dy = p.y - this.y ;

return Math.sqrt (dx*dx + dy*dy) ; // Math.sqrt est la racine carr´ee }

}

Nous avons partout explicit´e les acc`es aux variables d’instance et par this.x et this.y. Nous nous sommes aussi laiss´es aller `a employer la notation this(0,0) dans le constructeur sans arguments, cela permet de partager le code entre constructeurs.

On remarque que les champs x et y ne sont pas introduits par le mot-cl´e static. Chaque objet poss`ede ses propres champs, le programme

Pair p1 = new Pair (0, 1) ; Pair p2 = new Pair (2, 3) ;

System.out.println(p1.x + ", " + p2.y) ;

affiche (( 0, 3 )), ce qui est somme toute peu surprenant. De mˆeme, la m´ethode distance est propre `a chaque objet, ce qui est logique. En effet, si p est une autre paire, les distances p1.distance(p)etp2.distance(p)n’ont pas de raison particuli`eres d’ˆetre ´egales. Rien n’empˆeche de mettre des membres static dans une classe `a cr´eer des objets. Le concepteur de la classe Pair peut par exemple se dire qu’il est bien dommage de devoir cr´eer deux objets si on veut sim-plement calculer une distance. Il pourrait donc inclure la m´ethode statique suivante dans sa classe Pair.

static double distance(int x1, int y1, int x2, int y2) { int dx = x2-x1, dy = y2-y1 ;

return Math.sqrt(dx*dx+dy*dy) ; }

Il serait alors logique d’´ecrire la m´ethodedistancedynamique plutˆot ainsi :

double distance(Pair p) { return distance(this.x, this.y, p.x, p.y) ; } O`u bien sˆur, distance (this.x,. . . se comprend comme Pair.distance (this.x,. . .

2.3 H´eritage des objets, sous-typage

L’h´eritage dans toute sa puissance sera abord´e au cours suivant. Mais nous pratiquons d´ej`a l’h´eritage sans le savoir. En effet, les objets des classes que nous ´ecrivons ne d´emarrent pas tout nus dans la vie. Toute classe h´erite implicitement (pas besoin de extends, voir la section 1.5) de la classeObject. Les objets de la classeObject(et donc tous les objets) poss`edent quelques m´ethodes, dont la fameuse m´ethodetoString. Par cons´equent, le code suivant est accept´e par le compilateur, et s’ex´ecute sans erreur. Tout se passe exactement comme si nous avions ´ecrit une m´ethode toString, alors que cette m´ethode est en fait h´erit´ee.

Pair p = new Pair (1, 0) ; String repr = p.toString() ; System.out.print(repr) ;

Ce que renvoie la m´ethode toString desObjectn’est pas bien beau.

Pair@10b62c9

On reconnaˆıt le nom de la classe Pair suivi d’un nombre en hexad´ecimal qui est plus ou moins l’adresse dans la m´emoire de l’objet dont on a appel´e la m´ethode toString.

Mais nous pouvons red´efinir (override) la m´ethode toString dans la classe Pair. // public car il faut respecter la d´eclaration initiale

public String toString() {

return "(" + x + ", " + y + ")" ; }

Et l’affichage de p.toString() produit cette fois ci le r´esultat bien plus satisfaisant (1, 0).

Mˆeme si c’est un peu bizarre, il n’est au fond pas tr`es surprenant que lorsque nous appelons la m´ethode toString de l’int´erieur de la classe Pair comme nous le faisons ci-dessus, ce soit la nouvelle m´ethode toString qui est appel´ee. Mais ´ecrivons maintenant plus directement :

System.out.print(p) ;

Et nous obtenons une fois encore l’affichage(1, 0). Or, nous aurons beau parcourir la liste des neuf d´efinitions de m´ethodesprintsurcharg´ees de la classePrintStream, deprint(boolean b)

`

a print(Object obj), nous n’avons bien ´evidemment aucune chance d’y trouver une m´ethode print(Pair p).

Mais un objet de la classe Pair peut aussi ˆetre vu comme un objet de la classeObject. C’est le sous-typage : le type des objets Pair est un sous-type du type des objets Object (penser sous-ensemble : Pair ⊂Object). En Java, l’h´eritage entraˆıne le sous-typage, on dit alors plus synth´etiquement que Pair est unesous-classe de Object.

On note que le compilateur proc`ede d’abord `a un sous-typage (il se dit qu’un objet Pair est un objet Object), pour pouvoir r´esoudre la surcharge (il s´electionne la bonne m´ethodeprint parmi les neuf possibles). La conversion de type vers un sur-type est assez discr`ete, mais on peut l’expliciter.

System.out.print((Object)p) ; // Change le type explicitement

C’est donc finalement la m´ethode print(Object obj) qui est appel´ee. Mais `a l’int´erieur de cette m´ethode, on ne sait pas queobjest en fait un objet Pair. En simplifiant un peu, le code de cette m´ethode est ´equivalent `a ceci

public void print (Object obj) { i f (obj == null) {

this.print("null") ; } else {

this.print(obj.toString()) ;

} }

C’est-`a-dire que, au cas denull pr`es, la m´ethodeprint(Object obj)appelleprint(String s) avec comme argument obj.toString(). Et l`a, il y a une petite surprise : c’est la m´ethode toString() de la classe d’origine (la (( vraie )) classe de obj) qui est appel´ee, et non pas la m´ethode toString()des Object. C’est ce que l’on appelle parfois la liaison tardive.