Accueil > Programmer avec Java > Décomposition et modularité >
{}
du bloc qui le contiennent. Intentionnellement, la portée des variables est limitée pour mieux contrôler l’accès aux informations et pour permettre la réutilisation des mêmes noms de variables dans différentes parties du code source sans créer des conflits de noms.final
pour indiquer qu’elle ne peut pas être modifiée. Les constantes globales sont utilisées pour des valeurs qui ne changent pas dans le programme et qui sont utilisées dans plusieurs méthodes. Par exemples : une référence au Scanner de la console, des valeurs de référence, des codes de couleur ANSI, des constantes mathématiques. Nommant ce type de valeur dans une constante globale rend le code plus lisible que d’inclure la valeur littérale partout où elle est utilisée.À la fin de cette leçon vous devrez être en mesure de :
La portée d’une variable est synonyme de sa visibilité : jusqu’à quels endroits dans le code est-ce qu’on peut “voir” cette variable (la nommer sans erreur de syntaxe)?
Les variables globales sont visibles partout dans un programme1. Elles sont déclarées à l’extérieur des méthodes et on peut les lire et les modifier dans toutes les méthodes du programme.
C’est un avantage parce qu’on a pas besoin d’échanger cette information entre les méthodes via des paramètres ou des valeurs de retour.
C’est un désavantage parce n’importe quelle partie de notre code peut modifier cette information, ce qui peut rendre le code difficile à corriger, briser la modularité du code (les sous-problèmes sont maintenant fusionnés autour de l’information partagée) ou introduire des problèmes de sécurité (modifications possibles au-delà des intentions originales).
Les variables locales sont seulement visibles dans la méthode où elles sont définies. Les paramètres et les variables définies dans le corps d’une méthode sont des variables locales. Jusqu’à présent, nous avons seulement utilisé des variables locales dans nos programmes.
Les avantages des variables locales sont que : l’information existe seulement à l’endroit où elle sera directement utile et plusieurs variables dans un programme peuvent avoir le même nom sans interférence parce que leurs portées ne se croisent pas.
Le désavantage est que pour partager l’information locale à une autre méthode, il faut “passer des messages” avec des arguments/paramètres et des valeurs de retour. Cela peut parfois rendre la liste des paramètres assez longue que c’est difficile à se rappeler quel paramètre va à quelle position.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
---------------fichier------------------
déclaration de variable globale
méthode() {
déclaration de variable locale
}
méthode( les paramètres sont des variable locales ) {
}
----------------------------------------
C’est rarement une bonne idée d’utiliser des variables globales parce que le code devient beaucoup plus dur à déboguer. Mais il y a quelques cas où c’est une solution bien adaptée :
On peut alors déclarer une constante globale en le préfixant avec le mot-clé final
et en appliquant la convention du nom en majuscules. Les Scanner
pour la console, les codes de couleur ANSI et les structures de données (à venir dans une prochaine section du cours) sont des exemples de bons candidats de constantes globales.
Dans ce cas, le fait que la valeur soit constante élimine la possibilité de modification inappropriée et la portée globale peut réduire la complexité et la répétitivité des déclarations et appels de méthodes.
Plusieurs informations dans un jeu tombent dans cette catégorie. Si ces variables n’étaient pas globales, la liste des paramètres pour la plupart des méthodes deviendrait ingérable. De plus, la restriction avec Java d’une seule valeur de retour rend l’exportation de plus qu’une modification à ces valeurs, si pas impossible, du moins très complexe. Cette complexité additionnelle rendrait le code plus dur à lire, à déboguer et à modifier. Les risques liés aux variables globales sont alors moindre que les risques avec l’autre approche.
Fichier GlobalScanner.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import java.util.*;
final Scanner CONSOLE = new Scanner(System.in); // visible globalement
void main() {
// final Scanner CONSOLE = new Scanner(System.in); // ne serait pas visible dans getName ni getInteger
String name = getName();
int age = getInteger("age");
System.out.printf("%s a %d ans.\n", name, age);
}
String getName() {
System.out.print("Quel est votre nom? > ");
return CONSOLE.nextLine();
}
int getInteger(String what) {
System.out.printf("Entrez un nombre entier pour votre %s > ", what);
int result = CONSOLE.nextInt(); // pour consommer la valeur
CONSOLE.nextLine(); // pour consommer le retour de ligne aussi
return result;
}
Ici le Scanner pour la console est utilisée dans plusieurs méthodes et ne change pas de valeur (réfère toujours à System.in), alors c’est un bon candidat de constant globale.
Notez que la déclaration commence avec final
disant à Java que cette valeur ne peut jamais changer durant l’exécution. Notez aussi qu’on a écrit le nom en majuscules afin de mieux le repérer comme constante.
Fichier GlobalColorCodes.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import java.util.*;
final String RED = "\033[0;31m";
final String GREEN = "\033[0;32m";
final String YELLOW = "\033[0;33m";
final String RESET = "\033[0m";
void printRed(String text) {
System.out.println(RED + text + RESET);
}
void printGreen(String text) {
System.out.println(GREEN + text + RESET);
}
void printYellow(String text) {
System.out.println(YELLOW + text + RESET);
}
void main() {
printRed("Ceci est un message d'erreur!");
printGreen("Ceci est un message de succès!");
printYellow("Ceci est un avertissement");
System.out.printf("%sCe texte%s est en %ssurbrillance%s%s!!!%s",
GREEN, RESET, YELLOW, RESET, RED, RESET);
}
Ici on a plusieurs constantes globales pour les différents codes de couleur pour le texte à la console. Ces constantes sont utilisées dans les différentes méthodes, incluant main
qui fait un usage sur mesure pour la dernière phrase.
Voici un exemple banal pour quelques variables globales pour un jeu.
Fichier : GlobalGameState.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
int lives = 3;
int xp = 0;
boolean hasWeapon = true;
boolean hasHorse = true;
void diedInBattle() {
xp += 5;
lives--;
hasWeapon = false;
System.out.println("Vous êtes mort au combat et avez perdu votre arme!");
}
void diedWhileRiding() {
xp += 2;
lives--;
hasHorse = false;
System.out.println("Vous êtes mort en tombant de votre cheval!");
}
void showStats() {
System.out.println("Vies : " + lives);
System.out.println("Points d'expérience : " + xp);
System.out.println("Vous avez une arme : " + hasWeapon);
System.out.println("Vous avez un cheval : " + hasHorse);
}
void main() {
showStats();
diedInBattle();
showStats();
diedWhileRiding();
showStats();
}
Une des caractéristiques d’un programme avec différentes portées de variables est que le même nom peut être déclaré à plusieurs endroits sans conflits en autant que leurs portées soient différentes.
S’il y a deux variables du même type et du même nom, mais une est globale est l’autre est locale, la version locale sera toujours utilisée en priorité.
Voici quelques exemples courts pour illustrer la chose.
1
2
3
4
5
6
7
8
9
/* Globale versus locale */
String name = "Steve";
void main() {
String name = "Angelica";
name = "Naomi";
System.out.println(name);
}
Naomi. La variable locale dans main
masque la variable globale (qui n’est plus accessible dans main
à cause de ce masquage).
1
2
3
4
5
6
7
8
9
10
11
12
/* Locale versus locale */
void main() {
String name = "Angelica";
System.out.println(rename(name));
System.out.println(name);
}
String rename(String name) {
name = "Demonica";
return name;
}
Demonica. C’est la valeur de retour de rename
.
Angelica. Le paramètre name
est locale à la méthode rename
. Cette variable récoit la valeur de la variable dans main
comme argument. Par contre, ce paramètre (locale à rename
) n’existe plus après le retour de la méthode. rename
retourne la nouvelle valeur, mais la variable name
dans main
ne la reçoit pas. La valeur de retour passe plutôt directement dans un print. Alors la variable locale dans main
n’a subit aucune modification du début jusqu’à la fin.
1
2
3
4
5
6
7
void isolated() {
int answer = 7;
}
void main() {
System.out.println(answer); // erreur "cannot find symbol"
}
Ici, answer
est locale à la méthode isolated
, alors il n’est pas visible dans main
, contrairement à si cette variable avait été déclarée globalement, à l’extérieure d’une méthode.
1
2
3
4
5
int answer = 7; // déclaration globale
void main() {
System.out.println(answer); // answer est visible
}
1
2
3
4
5
6
7
8
String thing(String t) {
String t = t + "2"; // erreur "duplicate local variable"
return t;
}
void main() {
System.out.println(thing("thing"));
}
Ici, dans la méthode thing
on déclare déjà un paramètre String t
. Alors quand on déclare une autre variable locale du même nom à la ligne 2, Java nous indique une erreur. Si l’intention était de simplement modifier la valeur reçue dans le paramètre alors on peut réécrire la ligne 2 comme ceci :
1
2
3
4
5
6
7
8
String thing(String t) {
t = t + "2"; // référence valide au paramètre t déjà déclarée
return t;
}
void main() {
System.out.println(thing("thing"));
}
La différence est que la ligne 2 n’est plus une déclaration d’une nouvelle variable. Elle est désormais une référence à la variable déjà déclarée dans la liste de paramètres.
Faire les deux questions de choix multiples au début de la page ici pour vérifier votre compréhension de la portée des variables. Ne faites pas les problèmes de programmation car ils intègrent d’autres concepts que nous ne verrons pas dans ce cours.
La raison que les exercices de programmation sur la page sont déconseillés est qu’ils sollicitent des concepts de la programmation orientée objet que nous n’avons pas appris. Ce sont plutôt des concepts du cours ICS4U.
Je vous suggère plutôt les exercices pratiques suivants qui sont mieux adaptés à notre contexte.
Travaillez dans le répertoire GitHub partagé par votre enseignant pour la pratique et les exercices.
Cette pratique vise à démontrer les différences entre une approche de variables globales et une approche de variables locales dans un cas où les deux sont des choix valides.
Le contexte est l’utilisation d’un symbole spécifique comme indicateur d’invite de réponses chaque fois qu’on pose une question à l’utilisateur. On veut que ce symbole soit facile à changer si on veut utiliser un autre symbole. On veut aussi que le symbole soit utilisé dans plusieurs méthodes.
Avec cette approche, on déclare une variable globale pour le symbole. Par la suite nous concatenons ce symbole à la fin de chaque question posée à l’utilisateur. Pour montrer l’utilité à travers plusieurs méthodes, chaque question se trouve dans sa propre méthode.
Le Scanner de la console est également déclaré comme variable globale pour un accès direct pour toutes les méthodes.
Fichier
GlobalVar.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import java.util.*;
final Scanner CONSOLE = new Scanner(System.in);
final String PROMPT = " > ";
void main() {
String name = getName();
int age = getAge();
System.out.println("Votre nom est " + name + " et vous avez " + age + " ans.");
}
String getName(){
System.out.print("Quel est votre prénom?" + PROMPT);
String name = CONSOLE.next();
CONSOLE.nextLine(); //vider le reste de la ligne
return name;
}
int getAge() {
System.out.print("Quel est votre âge?" + PROMPT);
int age = CONSOLE.nextInt();
CONSOLE.nextLine();
return age;
}
GlobalVar.java
et l’exécuter pour voir comment il fonctionne.CONSOLE
, PROMPT
; name
, age
; name
; age
.PROMPT
pour utiliser un autre symbole que >
. Lancez le programme de nouveau pour voir le changement.main
et lancez le programme pour voir si ça change le fonctionnement. Selon vos observations, est-ce que la variable nommée name
dans main
est le même objet en mémoire que la variable nommée name
dans getName
?Avec cette approche, les variables à partager sont déclarées localement dans main
et partagé via le mécanisme argument-paramètre. De plus, au lieu de créer une variable pour le symbole prompt
, on crée une méthode. Parce que chaque méthode est visible par les autres dans le fichier, toutes les autres méthodes peuvent l’appeler.
Fichier
LocalVar.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import java.util.*;
void main() {
Scanner console = new Scanner(System.in);
String name = getName(console);
int age = getAge(console);
System.out.println("Votre nom est " + name + " et vous avez " + age + " ans.");
}
void prompt(String question) {
System.out.print(question + " > ");
}
String getName(Scanner in) {
prompt("Quel est votre nom?");
String name = in.next();
in.nextLine(); // vider le reste de la ligne
return name;
}
int getAge(Scanner in) {
prompt("Quel est votre âge?");
int age = in.nextInt();
in.nextLine();
return age;
}
LocalVar.java
et l’exécuter pour voir comment il fonctionne. Confirmez qu’il fonctionne - du point de vue de l’utilisateur - de manière identique à GlobalVar
.prompt()
. Est-ce que c’était plus facile ou difficile ou équivalent à ce qu’il fallait faire la dernière fois?GlobalVar
et les éléments que vous avez préférez dans LocalVar
. Énumérer aussi des éléments que vous n’avez pas aimé dans les deux versions. Ajoutez ces listes dans un commentaire d’en-tête dans un troisième fichier PreferredVar.java
. Combiner vos éléments préférés de chaque version dans cette troisième version ou apporter des modifications pour créer votre propre approche. Résoudre les erreurs de fusion et lancer ce troisième programme pour vérifier qu’à la console l’expérience reste identique pour l’utilisateur malgré les changements.Il y a des nuances à “partout dans un programme”, mais ces nuances ne sont pas importants dans ce cours. Pour votre curiosité, voici quand même un bref survol. Nos programmes tiennent dans un seul fichier .java, alors partout dans le fichier serait plus apte. Les fichiers .java représentent implicitement une classe Java. Alors partout dans la classe serait aussi plus précis. Mais les variables Java peuvent être qualifiées de publiques ou privées. Les variables privées sont seulement “globales” dans la classe, tandis que les variables publiques sont “globales” pour tout un package de classes (à l’extérieur de la classe elle-même). Ce concept s’étend à la portée des classes (publiques ou privées) dans les packages et la portée des packages (exportées ou non) dans des modules. ↩