• Aucun résultat trouvé

Retour sur les variables statiques

Nous avons défini au chapitre précédent ce qu'étaient les membres stati-ques en disant qu'ils appartenaient à une classe et non à une de ses instances.

Nous allons maintenant revenir sur cette notion à la lumière de ce que nous savons.

Lorsque, dans la définition d'une classe, une primitive est déclarée statique, il n'en existe qu'un seul exemplaire, quel que soit le nombre d'instances de cette classe créées (même si ce nombre est 0). Pour prendre une analogie, considérons la définition d'une classe comme le plan d'une voiture. Le nom-bre de sièges est une variable statique. En effet, il s'agit d'une caractéristi-que du modèle, et non de chacaractéristi-que exemplaire. Vous pouvez connaître la valeur de cette variable en regardant le plan. En revanche, la quantité d'es-sence restant dans le réservoir n'est pas une variable statique. Vous ne pou-vez pas la mesurer sur le plan, bien que son existence ait un sens. En effet, vous pouvez savoir, en consultant le plan, si vous pourrez interroger chaque exemplaire pour connaître la valeur de cette variable. Vous le pourrez si le plan indique que ce modèle de voiture possède une jauge de carburant.

La distinction variable de classe / variable d'instance devrait maintenant être claire. Nous pouvons donc la compliquer un peu.

Supposons que vous vouliez connaître la quantité d'essence restant dans le réservoir d'une voiture. Inutile de regarder sur le plan. Cette valeur ne con-cerne que les exemplaires, ou instances.

En revanche, si vous voulez connaître le nombre de sièges, vous pouvez consulter le plan, mais vous pouvez également consulter un exemplaire quelconque.

Nous avons vu qu'en Java, pour accéder à un membre d'un objet, il faut indiquer le nom de l'objet suivi du nom du membre, en séparant les deux à l'aide d'un point. Considérons maintenant l'exemple suivant :

public class VariablesStatiques {

public static void main(String[] argv) { Voiture maVoiture = new Voiture();

Voiture taVoiture = new Voiture();

System.out.println (maVoiture.puissanceMaxi);

System.out.println (taVoiture.puissanceMaxi);

System.out.println (Voiture.puissanceMaxi);

Voiture.puissanceMaxi = 300;

System.out.println (maVoiture.puissanceMaxi);

System.out.println (taVoiture.puissanceMaxi);

System.out.println (Voiture.puissanceMaxi);

maVoiture.puissanceMaxi = 200;

System.out.println (maVoiture.puissanceMaxi);

System.out.println (taVoiture.puissanceMaxi);

System.out.println (Voiture.puissanceMaxi);

} }

class Voiture {

static int puissanceMaxi = 250;

}

Ce programme affiche le résultat suivant :

250 250 250 300 300 300 200 200 200

Nous voyons ainsi que :

On peut accéder à un membre static par l'intermédiaire de la classe ou d'une instance. Il est néanmoins conseillé, pour optimiser la lisibilité des programmes, de s'en tenir à l'accès par la classe.

• Si on modifie la valeur d'une variable statique, elle est modifiée pour toutes les instances, même celles créées avant la modification. Cela est impliqué par le fait que la variable n'existe qu'à un seul exemplaire, partagé par la classe et toutes les instances.

• En modifiant une variable statique par l'intermédiaire d'une instance, (ce qui n'améliore pas la lisibilité du programme), on obtient le même résultat qu'en le faisant par l'intermédiaire de la classe.

Pour observer encore plus en détail ce principe, on peut modifier le pro-gramme précédent de la manière suivante :

public class VariablesStatiques2 {

public static void main(String[] argv) { Voiture maVoiture = new Voiture();

Voiture taVoiture = new Voiture();

System.out.print (maVoiture.moteur + " ");

System.out.println (maVoiture.moteur.puissanceMaxi);

System.out.print (taVoiture.moteur + " ");

System.out.println (taVoiture.moteur.puissanceMaxi);

System.out.print (Voiture.moteur + " ");

System.out.println (Voiture.moteur.puissanceMaxi);

Voiture.moteur.puissanceMaxi = 300;

System.out.print (Voiture.moteur + " ");

System.out.println (Voiture.moteur.puissanceMaxi);

System.out.print (taVoiture.moteur + " ");

System.out.println (taVoiture.moteur.puissanceMaxi);

System.out.print (Voiture.moteur + " ");

System.out.println (Voiture.moteur.puissanceMaxi);

maVoiture.moteur.puissanceMaxi = 200;

System.out.print (maVoiture.moteur + " ");

System.out.println (maVoiture.moteur.puissanceMaxi);

System.out.print (taVoiture.moteur + " ");

System.out.println (taVoiture.moteur.puissanceMaxi);

System.out.print (Voiture.moteur + " ");

System.out.println (Voiture.moteur.puissanceMaxi);

Moteur.puissanceMaxi = 150;

System.out.print (maVoiture.moteur + " ");

System.out.println (maVoiture.moteur.puissanceMaxi);

System.out.print (taVoiture.moteur + " ");

System.out.println (taVoiture.moteur.puissanceMaxi);

System.out.print (Voiture.moteur + " ");

System.out.println (Voiture.moteur.puissanceMaxi);

System.out.println (maVoiture);

System.out.println (taVoiture);

} }

class Voiture {

static Moteur moteur = new Moteur();

}

class Moteur {

static int puissanceMaxi = 250;

}

Ce programme ne présente d'autre intérêt que de nous permettre de vérifier ce que nous avons appris jusqu'ici. Nous avons maintenant deux classes en plus de notre programme principal : Voiture et Moteur.

Dans le programme principal, nous créons deux instances de Voiture : maVoiture et taVoiture. La classe Voiture possède cette fois un membre statique qui n'est plus une primitive, mais un objet. Cet objet possède lui-même un membre statique qui est une primitive de type int.

Note : Aucune de ces classes ne possède de constructeur explicite. (Rappe-lons qu'un constructeur est une méthode qui porte le même nom que la classe et qui est exécutée automatiquement lorsqu'une instance est créée.) Java utilise donc le constructeur par défaut pour créer les instances de Voi-ture et de Moteur.

Après avoir créé les deux instances de Voiture, nous affichons l'objet maVoiture.moteur :

System.out.print (maVoiture.moteur + " ");

Comme nous l'avons dit précédemment, afficher un objet consiste simple-ment à exécuter sa méthode toString() et à afficher le résultat. En l'absence de méthode toString() dans la classe Moteur, Java affiche par défaut (par un mécanisme qui sera expliqué en détail au prochain chapitre) le nom de la

classe et l'adresse en mémoire de l'objet. (Nous utilisons ici la méthode System.out.print() qui n'ajoute pas de fin de ligne, et nous ajoutons une chaîne littérale composée de trois espaces.)

La ligne suivante affiche la valeur de la primitive puissanceMaxi du mem-bre moteur de l'objet maVoiture.

System.out.println (maVoiture.moteur.puissanceMaxi);

Le programme fait ensuite la même chose pour l'objet taVoiture ainsi que pour la classe Voiture.

Les mêmes opérations sont ensuite répétées après avoir effectué les opéra-tions suivantes :

Voiture.moteur.puissanceMaxi = 300;

puis :

maVoiture.moteur.puissanceMaxi = 200;

et enfin :

Moteur.puissanceMaxi = 150;

La première de ces opérations modifie l'instance moteur dans la classe Voiture. La deuxième modifie l'instance moteur dans l'instance maVoiture.

Enfin, la troisième modifie directement la valeur de puissanceMaxi dans la classe Moteur.

Enfin, à la fin du programme, nous affichons les objets maVoiture et taVoiture :

System.out.println (maVoiture);

System.out.println (taVoiture);

Si nous exécutons ce programme, nous obtenons le résultat suivant :

Moteur@10f8011b 250 Moteur@10f8011b 250 Moteur@10f8011b 250 Moteur@10f8011b 300 Moteur@10f8011b 300 Moteur@10f8011b 300 Moteur@10f8011b 200 Moteur@10f8011b 200 Moteur@10f8011b 200 Moteur@10f8011b 150 Moteur@10f8011b 150 Moteur@10f8011b 150 Voiture@10f4011b Voiture@10f0011b

Nous voyons immédiatement qu'il n'existe, à tous moments du déroulement du programme, qu'un seul objet instance de la classe Moteur, à l'adresse 10f8011b. En revanche, il existe bien deux instances différentes de la classe Voiture, l'une à l'adresse 10f4011b et l'autre à l'adresse 10f0011b.

Note : Toutes ces adresses changent à chaque exécution du programme et seront forcément différentes dans votre cas.

Attention : Nous venons de voir qu'il est possible d'utiliser des références différentes telles que :

maVoiture.moteur.puissanceMaxi taVoiture.moteur.puissanceMaxi Voiture.moteur.puissanceMaxi

En revanche, les références suivantes sont impossibles :

maVoiture.Moteur.puissanceMaxi Voiture.Moteur.puissanceMaxi

En effet, Moteur fait référence à une classe. Or, la notation utilisant le point comme séparateur indique une hiérarchie d'inclusion. L'expression :

maVoiture.moteur.puissanceMaxi

signifie que l'objet maVoiture possède un membre appelé moteur qui pos-sède un membre appelé puissanceMaxi. De la même façon, la référence :

maVoiture.Moteur.puissanceMaxi

signifierait que l'objet maVoiture possède un membre Moteur qui possède un membre puissanceMaxi, ce qui est manifestement faux, puisque Mo-teur est une classe et que cette classe n'est pas un membre de l'objet maVoiture.

De la même façon, la référence :

Voiture.Moteur.puissanceMaxi

signifie que la classe Voiture possède un membre Moteur (qui est lui-même une classe) qui possède un membre puissanceMaxi, ce qui ne cor-respond pas non plus à la hiérarchie créée par notre programme.

Note : Il faut distinguer les différents types de hiérarchies. Ici, il s'agit d'une hiérarchie basée sur la possession d'un membre, ou composition. Cela n'a rien à voir avec la hiérarchie liée à la généalogie des classes, que nous étudierons au chapitre suivant, pas plus qu'avec celle de la propagation des événements, qui fera l'objet d'une étude ultérieure. Le type de relation im-pliquée par la hiérarchie décrite ici est parfois appelé "a un" (en anglais,

"has a") par opposition à la relation mise en œuvre dans la hiérarchie gé-néalogique ou héritage, appelée "est un" (en anglais "is a").

Ainsi, on peut dire que les objets de la classe Voiture ont un membre de la classe Moteur, ce qui permet d'écrire Voiture.moteur ou maVoiture.mo-teur mais ni les objets instances de la classe Voiture ni la classe Voiture elle-même ne contiennent la classe Moteur, ce qui interdit d'écrire Voiture.Moteur ou maVoiture.Moteur. (Nous verrons au chapitre suivant qu'une classe peut, d'une certaine façon, contenir une autre classe, mais ce n'est pas le cas ici.)

En revanche, l'écriture suivante est possible :

Voiture.(Moteur.puissanceMaxi)

car (Moteur.puissanceMaxi) est une référence à une primitive statique, alors que :

Voiture.Moteur.puissanceMaxi

est l'équivalent de :

(Voiture.Moteur).puissanceMaxi

Toutefois, il n'est pas possible d'écrire :

maVoiture.(Moteur.puissanceMaxi);

ce qui produit une erreur de compilation.

Masquage des identificateurs de type static

Nous avons vu précédemment que des identificateurs pouvaient être mas-qués. Les identificateurs de type static peuvent être masqués comme les autres. Examinez le programme suivant :

public class VariablesStatiques4 {

public static void main(String[] argv) {

Voiture maVoiture = new Voiture(350);

System.out.println (maVoiture.puissance);

} }

class Voiture {

static int puissance = 300;

Voiture (int puissance) {

System.out.println (puissance);

System.out.println (Voiture.puissance);

} }

Si vous exécutez ce programme, vous obtiendrez le résultat suivant :

350 300 300

Ici, la classe Voiture est dotée d'un constructeur. La première ligne de ce constructeur affiche la valeur du paramètre puissance, passé lors de la créa-tion de l'instance. Ce paramètre ayant masqué le membre statique qui porte le même nom, comme on peut le voir sur la première ligne de l'affichage.

En revanche, le membre statique est toujours accessible en y faisant réfé-rence à l'aide du nom de la classe, comme le montre la ligne suivante de l'affichage. Dès que l'on est hors de la portée du paramètre qui masquait le membre statique, celui-ci redevient disponible normalement comme le montre la troisième ligne de l'affichage, provoquée par la ligne :

System.out.println (maVoiture.puissance);

de la méthode main().

Vous vous demandez peut-être ce que donnerait cette ligne si elle était exé-cutée à l'intérieur du constructeur, comme dans l'exemple ci-dessous :

public class VariablesStatiques4 {

public static void main(String[] argv) { Voiture maVoiture = new Voiture(350);

System.out.println (maVoiture.puissance);

} }

class Voiture {

static int puissance = 300;

Voiture (int puissance) {

System.out.println (puissance);

System.out.println (Voiture.puissance);

// La ligne suivante produit une erreur : System.out.println (maVoiture.puissance);

} }

Ce programme ne peut pas être compilé. En effet, il produit une erreur car la référence à l'instance maVoiture est impossible puisque la construction de cet objet n'est pas terminée. Pourtant, l'objet en question est parfaite-ment accessible. Seule sa référence à l'aide du handle maVoiture ne peut être résolue. En revanche, vous pouvez y faire référence à l'aide du mot clé this, qui sert à désigner l'objet dans lequel on se trouve. Ainsi modifié, le programme devient :

public class VariablesStatiques4 {

public static void main(String[] argv) { Voiture maVoiture = new Voiture(350);

System.out.println (maVoiture.puissance);

} }

class Voiture {

static int puissance = 300;

Voiture (int puissance) {

System.out.println (puissance);

System.out.println (Voiture.puissance);

System.out.println (this.puissance);

} }

et peut être compilé sans erreur. Son exécution produit le résultat suivant :

350 300 300 300

ce qui confirme le masquage du membre statique.

Note : Vous ne devez pas croire tout ce que l'on vous dit sur parole. Il vaut toujours mieux vérifier par vous-même. Pouvez-vous imaginer un moyen de vérifier que this fait bien référence à l'objet maVoiture ? (Réponse à la fin de ce chapitre.)