• Aucun résultat trouvé

class Point {

private int x, y;

public int getX() { return x; } public int getY() { return y; } public void setPos(int a, int b) {

 validation de a et b  x = a; y = b; }

... }

avec cette d´efinition, pour transformer un point p en son sym´etrique par rapport `a l’axe des abscisses il faut ´ecrire :

p.setPos(p.getX(), - p.getY());

Malgr´e les apparences, la deuxi`eme mani`ere est la meilleure, car elle respecte le principe d’encapsulation, ce que ne fait pas la premi`ere. D’abord, cette deuxi`eme d´efinition permet de garantir la coh´erence interne des objets cr´e´es par les utilisateurs de la classe Point, y compris les plus ´etourdis, puisque le seul moyen de modifier les valeurs des variables d’instance x et y est d’appeler la m´ethode setPos, dans laquelle le concepteur de la classe a ´ecrit le code qui valide ces valeurs (partie validation de a et b ).

Ensuite, la deuxi`eme d´efinition garantit qu’aucun d´eveloppement utilisant les objets Point ne fera in- tervenir des d´etails de l’impl´ementation de cette classe, puisque les variables d’instance x et y sont priv´ees, c’est-`a-dire inaccessibles. Par exemple, si un jour il devenait n´ecessaire de repr´esenter les points par leurs coordonn´ees polaires au lieu des coordonn´ees cart´esiennes, cela pourrait ˆetre fait sans rendre invalide aucun programme utilisant la classe Point puisque, vu de l’ext´erieur, le comportement de la classe Point serait maintenu :

class Point {

private double rho, theta;

public int getX() { return (int) (rho * Math.cos(theta)); } public int getY() { return (int) (rho * Math.sin(theta)); } public void setPos(int a, int b) {

rho = Math.sqrt(a * a + b * b); theta = Math.atan2(b, a); }

... }

6.5

Initialisation des objets

En Java la cr´eation d’un objet est toujours dynamique : elle consiste en un appel de l’op´erateur new, et peut se produire `a n’importe quel endroit d’un programme. Il faut savoir que les variables d’instance des objets ne restent jamais ind´etermin´ees, elles ont des valeurs initiales pr´ecises.

Un ´el´ement de la m´ethodologie objet, tr`es important pour la qualit´e des programmes, est que le concepteur d’une classe maˆıtrise compl`etement les actions `a faire et les valeurs initiales `a donner lors de la cr´eation des instances ; il peut ainsi garantir que les objets sont, d`es leur cr´eation, toujours coh´erents.

Notons pour commencer que, s’il n’y a pas d’autres indications, Java donne une valeur initiale `a chaque membre d’une classe. Pour les membres de types primitifs, la valeur initiale correspondant `a chaque type est celle qu’indique le tableau 1 (page 10). Pour les membres d’autres types, c’est-`a-dire les tableaux et les objets, la valeur initiale est null. Ainsi, sans que le programmeur n’ait `a prendre de pr´ecaution particuli`ere, les instances d’une classe d´eclar´ee comme ceci

class Point { int x, y; ... }

sont des points ayant (0, 0) pour coordonn´ees.

Lorsque l’initialisation souhait´ee est plus complexe que l’initialisation par d´efaut, mais assez simple pour s’´ecrire comme un ensemble d’affectations ind´ependantes de l’instance particuli`ere cr´e´ee, on peut employer une notation analogue `a celle de C. Par exemple, voici une classe Point dont les instances sont des points al´eatoirement dispos´es dans le rectangle [0, 600[×[0, 400[ :

class Point {

int x = (int) (Math.random() * 600); int y = (int) (Math.random() * 400); ...

}

Lorsque l’initialisation requise est plus complexe qu’une suite d’affectations, ou lorsqu’elle d´epend d’´el´ements li´es au contexte dans lequel l’objet est cr´e´ee, la notation pr´ec´edente est insuffisante. Il faut alors d´efinir un ou plusieurs constructeurs, comme expliqu´e `a la section suivante.

6.5.1 Constructeurs

Un constructeur d’une classe est une m´ethode qui a le mˆeme nom que la classe et pas de type du r´esultat33.

Exemple :

class Point {

private int x, y;

public Point(int a, int b) { validation de a et b x = a; y = b; } ... }

Un constructeur est toujours appel´e de la mˆeme mani`ere : lors de la cr´eation d’un objet, c’est-`a-dire comme op´erande de l’op´erateur new :

Point p = new Point(200, 150);

Dans un constructeur on ne trouve pas d’instruction return : un constructeur ne rend rien (c’est l’op´erateur new qui rend quelque chose, `a savoir une r´ef´erence sur l’objet nouveau), son rˆole est d’initialiser les variables d’instance de l’objet en cours de cr´eation, et plus g´en´eralement d’effectuer toutes les op´erations requises par la cr´eation de l’objet.

Comme les autres m´ethodes, les constructeurs peuvent ˆetre qualifi´es public, protected ou private, ou avoir l’accessibilit´e par d´efaut. Une situation utile et fr´equente est celle montr´ee ci-dessus : un ou plusieurs constructeurs publics servant surtout `a initialiser un ensemble de variables d’instance priv´ees.

La surcharge des m´ethodes vaut pour les constructeurs : une classe peut avoir plusieurs constructeurs, qui devront alors diff´erer par leurs signatures :

class Point {

private int x, y;

public Point(int a, int b) { validation de a et b x = a; y = b; } public Point(int a) { validation de a x = a; y = 0; } ... }

A l’int´erieur d’un constructeur l’identificateur this utilis´e comme un nom de variable d´esigne (comme dans les autres m´ethodes d’instance) l’objet en cours de construction. Cela permet parfois de lever certaines ambigu¨ıt´es ; par exemple, le constructeur `a deux arguments donn´e plus haut peut s’´ecrire aussi34 :

33Nous l’avons d´ej`a dit, le type du r´esultat d’une m´ethode ne fait pas partie de sa signature. N´eanmoins, le fait d’avoir ou

non un type du r´esultat est un ´el´ement de la signature. Il en d´ecoule que, par exemple, dans une certaine classe Point peuvent coexister un constructeur Point() et une m´ethode void Point() distincts.

34Parfois les noms des arguments des m´ethodes ne sont pas indiff´erents, ne serait-ce qu’en vue de la documentation (ne pas

6 LES OBJETS 6.5 Initialisation des objets

class Point {

private int x, y;

public Point(int x, int y) { validation de x et y this.x = x; this.y = y; } ... }

A l’int´erieur d’un constructeur, l’identificateur this, utilis´e comme un nom de m´ethode, d´esigne un autre constructeur de la mˆeme classe (celui qui correspond aux arguments de l’appel). Par exemple, voici une autre mani`ere d’´ecrire le second constructeur de la classe Point montr´ee plus haut :

class Point {

private int x, y;

public Point(int x, int y) { validation de x et y this.x = x; this.y = y; ... } public Point(int x) { this(x, 0); ... } ... }

Note 1. Lorsqu’un tel appel de this(...) apparaˆıt, il doit ˆetre la premi`ere instruction du constructeur. Note 2. L’expression qui cr´ee un objet, new UneClasse() , se pr´esente toujours comme l’appel d’un constructeur, mˆeme lorsque le programmeur n’a ´ecrit aucun constructeur pour la classe en question. Tout se passe comme si Java avait dans ce cas ajout´e `a la classe un constructeur implicite, r´eduit `a ceci :

UneClasse() { super(); }

(la signification de l’expression super() est explique `a la section 7.4) 6.5.2 Membres constants (final)

La d´eclaration d’un membre d’une classe peut commencer par le qualifieur final. Nous verrons `a la section 7.5.3 ce que cela veut dire pour une m´ethode ; pour une variable, cela signifie qu’une fois initialis´ee, elle ne pourra plus changer de valeur ; en particulier, toute apparition de cette variable `a gauche d’une affectation sera refus´ee par le compilateur. Une telle variable est donc plutˆot une constante.

Par exemple, voici comment d´eclarer une classe dont les instances sont des points immuables : class Point { final int x, y; Point(int a, int b) { validation de a et b x = a; y = b; } ... }

N.B. Les affectations de x et y qui apparaissent dans le constructeur sont accept´ees par le compilateur, car il les reconnaˆıt comme des initialisations ; toute autre affectation de ces variables sera refus´ee.

Le programmeur a donc deux mani`eres d’assurer que des variables d’instance ne seront jamais modifi´ees une fois les instances cr´ees : qualifier ces variables final ou bien les qualifier private et ne pas d´efinir de m´ethode qui permettrait de les modifier. La premi`ere mani`ere est pr´ef´erable, car

– la qualification private n’empˆeche pas qu’une variable soit modifi´ee depuis une m´ethode de la mˆeme classe,

– la qualification final informe le compilateur du caract`ere constant de la variable en question, celui-ci peut donc effectuer les optimisations que ce caract`ere constant permet.

Constantes de classe. Les variables de classe peuvent elles aussi ˆetre qualifi´ees final ; elles sont alors tr`es proches des pseudo-constantes  qu’on d´efinit en C par la directive #define. La coutume est de nommer ces variables par des identificateurs tout en majuscules :

public class Point {

public static final int XMAX = 600; public static final int YMAX = 400; private int x, y;

public Point(int x, int y) throws Exception { if (x < 0 || x >= XMAX || y < 0 || y > YMAX)

throw new Exception("Coordonn´ees invalides pour un Point"); this.x = x;

this.y = y; }

... }

6.5.3 Blocs d’initialisation statiques

Les constructeurs des classes sont appel´es lors de la cr´eation des objets, ce qui est tout `a fait adapt´e `a l’initialisation des variables d’instance. Mais comment obtenir l’initialisation d’une variable de classe, lorsque cette initialisation est plus complexe qu’une simple affectation ?

Un bloc d’initialisation statique est une suite d’instructions, encadr´ee par une paire d’accolades, pr´ec´ed´ee du mot static. Ces blocs (et les autres affectations servant `a initialiser des variables de classe) sont ex´ecut´es, dans l’ordre o`u ils sont ´ecrits, lors du chargement de la classe, c’est-`a-dire avant toute cr´eation d’une instance et avant tout acc`es `a une variable ou `a une m´ethode de classe. Exemple :

public class Point {

public static int xMax; public static int yMax; static {

Dimension taillEcran = Toolkit.getDefaultToolkit().getScreenSize(); xMax = taillEcran.width;

yMax = taillEcran.height; }

... }

6.5.4 Destruction des objets

Dans beaucoup de langages, comme C et C++, l’op´eration de cr´eation des objets (malloc, new, etc.) est coupl´ee `a une op´eration sym´etrique de lib´eration de la m´emoire (free, delete, etc.) que le programmeur doit explicitement appeler lorsqu’un objet cesse d’ˆetre utile. Dans ces langages, l’oubli de la restitution de la m´emoire allou´ee est une cause d’erreurs sournoises dans les programmes. Or, lorsque les objets sont complexes, la lib´eration compl`ete et correcte de la m´emoire qu’ils occupent, effectu´ee par des m´ethodes appel´ees destructeurs, est un probl`eme souvent difficile.