Cours initiation à la programmation Java notions de base
...
Dans le chapitre précédent, nous avons étudié un modèle abstrait simple de l’ordinateur. Ce modèle de bas niveau convient pour une description générale des ordinateurs. Dans la pratique, chaque langage propose son propre modèle abstrait, décrivant en particulier l’organisation de la mémoire et les instructions compréhensibles par le processeur. Dans le présent chapitre, nous allons commencer l’étude de l’ordinateur abstrait spécifique au langage Java.
Nous consacrerons plus particulièrement ce chapitre à la mémoire. En effet, elle stocke les informations que le processeur manipule. Sans stockage, on ne peut pas écrire de programme. Nous verrons donc comment la mémoire est organisée pour Java et nous étudierons la notion de variable. Les variables permettent à un programme d’imposer une organisation adaptée de la mémoire, ainsi qu’une interprétation de son contenu (nombres entiers, nombres réels, texte, etc.). Le langage utilisé dans un programme détermine d’ailleurs les interprétations possibles par l’intermédiaire de la notion de type.
Après avoir compris le modèle de la mémoire en Java, nous présenterons deux instructions très importantes, briques élémentaires de tout programme. La déclaration de variable permet à un programme de préciser les variables qu’il souhaite utiliser, c’est-à-dire l’utilisation de la mémoire qu’il envisage. L’affectation permet de placer dans la mémoire les informations à manipuler. Nous verrons en particulier comment faire faire des calculs au processeur.
2.1 Mémoire et variables
2.1.1 Modèle de la mémoire en Java
Nous avons vu dans le chapitre précédent que la mémoire de l’ordinateur est capable de stocker des chiffres binaires, c’est-à-dire des 0 et des 1. En Java, la mémoire est de plus organisée. On considère qu’elle est constituée d’un ensemble de cases identiques. Chaque case permet de stocker un octet1 c’est-à-dire une suite de 8 chiffres binaires, aussi appelés bits.
De plus, la mémoire n’est pas manipulée directement sous sa forme élémentaire par les programmes Java. En effet, comment peut-on faire par exemple pour représenter un texte en français
à partir de chiffres binaires (si on veut utiliser l’ordinateur pour écrire une lettre) ? La réponse à cette question est typiquement un détail technique dont l’utilisateur n’a pas besoin de s’occuper.
De ce fait, le modèle d’ordinateur abstrait de Java permet de manipuler la mémoire de façon plus évoluée que sous sa forme élémentaire.
2.1.2 Regroupement de cases et notion de type
Il semble évident que pour stocker dans la mémoire une valeur entière comprise par exemple entre 0 et 1000, une seule case ne va pas suffire. Le nombre de cases élémentaires nécessaire pour stocker une information dépend donc de la nature de l’information considérée.
L’ordinateur abstrait de Java possède la facultéd’associer à chaque information qu’il manipule un type, qui indique la nature de cette information. Il peut ainsi manipuler diverses informations de natures différentes comme des nombres entiers, des nombres réels, des caractères, etc. Il sait exactement combien de cases de la mémoire il doit utiliser pour stocker une valeur d’une certaine nature. L’utilisateur n’a pas besoin de savoir comment l’ordinateur découpe l’information afin de la répartir dans les différentes cases car ce dernier possède des instructions qui permettent de manipuler les valeurs en question en tenant compte de leur nature.
Voici la liste des types directement manipulables en Java. Ce sont les types fondamentaux :
Nom du type Description Cases
byte entier compris entre -128 et 127 (27 − 1) 1
short entier compris entre -32768 et 32767 (215 − 1) 2
int entier compris entre -2147483648 et 2147483647 (231 − 1) 4
long entier compris entre −(263) et 263 − 1 8
float nombre réel en simple précision (environ 7 chiffres significatifs) 4
double nombre réel en double précision (environ 15 chiffres significatifs) 8
boolean la valeur true ou false 12
char un caractère 2
Nous verrons plus tard que chaque type est associé à un certain nombre d’opérations. Dans un programme, l’utilisation d’une valeur d’un certain type se fera grâce aux instructions possibles pour ce type. On verra ainsi comment additionner deux valeurs entières, etc.
REMARQUE
Dans la pratique, les types les plus importants sont les types int et boolean car ils interviennent directement dans les structures de contrôle qui permettent d’écrire des programmes intéressants (voir les chapitres 4 et 5).
En général, les calculs numériques sont réalisés grâce au type double. Enfin, l’interaction avec l’utilisateur passe par les chaînes de caractères et donc par le type char. On peut dire que les autres types sont beaucoup moins utilisés.
A titre illustratif, voici quelques exemples de représentation binaire :
...
On constate que la représentation binaire n’est pas très “parlante”! Fort heureusement, nous n’aurons pas à nous en servir car Java permet l’utilisation directe des valeurs numériques usuelles (entières et réelles). Il n’est d’ailleurs pas prévu de pouvoir indiquer simplement dans un programme Java la représentation binaire d’une valeur. Excepté pour le cas un peu particulier des chars (cf la section 2.5.3), le langage Java masque complètement le codage des valeurs utilisées.
2.1.3 Les variables
En Java, la mémoire est en fait un ensemble de groupes de cases. Chaque groupe de cases représente une information dont l’ordinateur connaît le type. Le problème est maintenant de trouver un moyen pour l’utilisateur de manipuler ces informations. Pour ce faire, il faut que le programme (plus précisément les instructions du programme) puisse désigner un groupe de cases particulier. Le problème est résolu par la notion de variable :
Définition 2.1 Une variable est un groupe de cases de la mémoire de l’ordinateur abstrait qui contient une valeur dont la nature est déterminée par le type du groupe de cases (appelé type de la variable). Chaque variable est désignée par un nom, qui doit être un identificateur valide.
Dans une instruction du programme, l’utilisateur peut faire référence à une variable en utilisant son identificateur. Comme l’ordinateur abstrait associe à l’identificateur le type de la variable, on est sûr que l’instruction va interpréter correctement ce groupe (et ne pas prendre un caractère pour un entier par exemple).
Il est important de noter que l’organisation de la mémoire est spécifique à chaque programme. Deux programmes différents utiliseront la mémoire de façon différente, bien que la mémoire abstraite sous-jacente soit la même. De ce fait, chaque programme doit comporter des instructions qui indiquent au processeur comment organiser la mémoire, c’est-à-dire qui décrivent les variables que les autres instructions pourront utiliser.
Il faut aussi noter que l’utilisation des variables est l’unique moyen pour le processeur de manipuler la mémoire.
2.1.4 Déclaration de variables
Avant d’utiliser une variable, un programme doit commencer par donner le nom et le type de celle-ci, grâce à une instruction de déclaration.
Exemple 2.1 :
Supposons par exemple, que nous souhaitions écrire un programme qui calcule la moyenne de deux nombres entiers. Nous devons utiliser trois variables, une pour chaque nombre entier et une pour le résultat du calcul (la moyenne, qui n’est pas nécessairement entière). Le début du programme comportera alors les lignes suivantes :
int premier; int second;
double moyenne;
La figure 2.1 montre comment on peut représenter l’effet sur la mémoire de la déclaration de variables. Nous sommes bien loin ici de la réalité physique de l’ordinateur. La mémoire est considérée simplement comme l’ensemble des variables déclarée. Le groupe de cases mémoire correspondant à une variable est représenté par une seule case dans laquelle on indique le type de la variable (puis sa valeur, comme on le verra par la suite). La case pour la variable de type double est deux fois plus grosse que celles utilisées par les variables de type int, afin de rappeler qu’un double occupe huit octets, alors qu’un int occupe seulement 4 octets (en général, on simplifie la présentation sans faire ce rappel). Enfin, l’identificateur de la variable est indiqué devant celle-ci. Dans la réalité, l’effet de la déclaration d’une variable est beaucoup plus complexe, mais cette représentation est largement suffisante pour pouvoir programmer.
premier
second int
int
moyenne double
FIG. 2.1 – Déclaration de variables
La déclaration d’une variable consiste simplement à indiquer son type suivi d’un espace puis du nom de la variable (un identificateur) et se termine par un point virgule. De façon générale une déclaration de variable a la forme suivante :
type identificateur ;
Il est possible de simplifier la déclaration des variables qu’on souhaite utiliser dans un programme en regroupant entre elles les déclarations de variables du même type.
Exemple 2.2 :
Reprenons l’exemple 2.1. Dans celui-ci, on déclare deux variables de type int. Il est possible de remplacer les deux lignes de déclaration par l’unique ligne suivante :
int premier, second;
Le principe d’une déclaration multiple est très simple. Au lieu de donner un seul identificateur par ligne, on en donne autant qu’on souhaite, séparés entre eux par des virgules. La signification de la déclaration est simple, une variable du type indiqué sera créé pour chaque identificateur. La forme générale est ainsi :
type identificateur_1, identificateur_2,..., identificateur_n ;
3Nous indiquons ici le morceau intéressant du programme. Il ne s’agit pas d’un programme complet.
REMARQUE ✆
L’écriture précédente n’est pas utilisable telle quelle. Il est impossible dans un programme d’utiliser une ellipse pour indiquer qu’on veut un nombre de variable dépendant d’un paramètre (ici n). Il s’agit simplement d’une facilité d’écriture pour indiquer qu’un nombre arbitraire d’identificateurs peut être utilisé.
2.1.5 Conventions pour les noms de variables
Pour les noms de variables, nous utiliserons les mêmes conventions que pour les noms de programmes (cf la section 1.3.4), à une exception près : la première lettre du nom d’une variable sera toujours une minuscule. Par exemple on écrira noteDeMath plutôt que note-de-math ou toute autre solution.
REMARQUE
Les conventions sont très importantes car elles sont suivies par l’ensemble (ou presque) des programmeurs Java. De ce fait, elles permettent de relire facilement un programme et surtout de se souvenir plus facilement des noms des variables. Si on écrit un programme dans lequel on manipule une “note de math”, la convention précise que seul l’identificateur noteDeMath sera utilisé. La mémorisation d’une seule règle permet ensuite de s’intéresser au nom usuel de la variable, sans avoir à attacher d’importance à l’aspect typographique.
2.2 Affectation
2.2.1 Valeurs littérales
Comme nous l’avons souvent répété, l’ordinateur manipule des informations. Il est donc indispensable de pouvoir décrire dans un programme ces informations. Si on veut par exemple se servir de l’ordinateur pour faire des calculs, on doit pouvoir placer dans les variables des valeurs numériques entières ou réelles. De façon générale, il est utile de pouvoir donner dans un programme une valeur correspondant à un type fondamental. L’ordinateur abstrait possède des règles permettant d’écrire des valeurs correspondant à chacun des types fondamentaux donnés dans la section 2.1.2. Une telle valeur s’appelle une valeur littérale.
Les valeurs littérales ne sont pas des instructions. Elles seront utilisées à l’intérieur d’instruction comme l’affectation mais ne peuvent en aucun cas apparaître seules.
Valeurs littérales entières
Quand on écrit un entier de façon usuelle, l’ordinateur abstrait le considère comme une valeur littérale de type int. On écrira donc dans un programme 123, 2414891, etc. En fait, toute suite (par trop longue!) de chiffres est une valeur littérale de type int.
Si on souhaite donner une valeur littérale de type long, on doit faire suivre l’entier de la lettre l ou L. Par exemple, 2l ne désigne pas la valeur entière 2 représentée comme un int, mais représentée comme un long (on utilise donc 8 octets au lieu de 4).
Il n’existe aucun moyen de donner une valeur littérale de type byte ou short. L’écriture 2b ne signifie en aucun cas la valeur entière 2 représentée comme un byte. En fait, elle n’est pas acceptée par l’ordinateur. Nous verrons à la section 2.3.5 qu’on peut quand même utiliser les types byte et short moyennant certaines précautions.
Valeurs littérales réelles
Quand on écrit un nombre réel de façon usuelle (comme sur une calculatrice), l’ordinateur le considère comme une valeur littérale de type double. Rappelons que la partie fractionnaire d’un nombre réel est séparée de sa partie entière par un point (notation anglo-saxonne classique). On écrit par exemple 0.001223 ou encore 454.7788. On peut également utiliser une notation “scientifique” où1.35454e-5 par exemple désigne 1.35454 10−5 représenté sous forme d’un double (e pouvant être remplacé par E).
Pour obtenir une valeur littérale de type float, on fait suivre l’écriture du réel par la lettre f ou F. Par exemple, 2.5f désigne le réel 2.5 représenté sous forme d’un float, c’est-à-dire en utilisant
4 octets, alors que 2.5 désigne le même réel mais sous forme d’un double, c’est-à-dire représenté par 8 octets. Notons qu’il est possible de faire suivre un réel par la lettre d ou D, ce qui indique (de façon redondante) que c’est une valeur littérale de type double.
Autres valeurs littérales
Pour le type boolean qui représente une valeur de vérité, il existe deux valeurs littérales seulement : true et false (c’est-à-dire vrai et faux).
Pour le type char qui représente un caractère, on donne une valeur littérale simplement en encadrant le caractère considéré entre deux apostrophes. On écrit ainsi ’a’, ’2’, ’@’, etc. Pour des détails sur les caractères, on pourra se reporter à la section 2.5.3.
Récapitulation
Pour les valeurs numériques, voici une récapitulation des règles appliquées :
Valeur littérale Type
entière seule int
entière suivie d’un l ou d’un L long
entière suivie d’un f ou d’un F float
entière suivie d’un d ou d’un D double
réelle seule double
réelle suivie d’un f ou d’un F float
réelle suivie d’un d ou d’un D double
On remarque que les modificateurs d et f peuvent s’appliquer aux entiers et les faire considérer comme des doubles ou des floats. Il faut bien garder à l’esprit qu’un entier est un cas particulier de réel!
Exemple 2.3 :
Voici un tableau qui illustre ces règles :
valeur 2 2.0 2.0f 2.0d 2l 2f 2d
type int double float double long float double
Il est important de noter que la lettre éventuelle suit directement le nombre. Il ne peut pas y avoir d’espace entre les deux.
REMARQUE
Comme nous l’avons indiqué précédemment, les types int et double sont les plus importants des types numériques. Or, les règles ci-dessus indiquent que ce sont justement les valeurs littérales les plus simples à écrire. L’emploi des modificateurs (comme f par exemple) sera donc peu fréquent.
2.2.2 Affectation d’une valeur littérale à une variable
Après la déclaration, la seconde instruction que nous allons étudier est l’affectation. Nous avons jusqu’à présent parlé du rôle général de l’ordinateur comme instrument de manipulation d’informations. Nous savons que ces informations sont stockées dans la mémoire grâce à des variables. Il nous reste maintenant à être capables de placer des informations dans une variable. Pour ce faire on utilise une instruction d’affectation.
Exemple 2.4 :
Reprenons l’exemple du calcul de la moyenne de deux nombres que nous supposerons réels cette fois-ci (exemple 2.1). Pour pouvoir faire cette moyenne, il faut donner la valeur des réels en question. Pour ce faire, nous écrivons la partie de programme suivante :
double premier, second, moyenne; premier = 2.3434; second = -23.4e-1;
Le sens de la première ligne est déjà connu, il s’agit de la déclaration des variables que nous souhaitons utiliser. Les dernières lignes s’interprètent de la façon suivante : chaque ligne du programme indique au processeur de placer la valeur littérale à droite du signe égal dans la variable dont l’identificateur apparaît à gauche du signe égal. Donc après exécution des deux lignes, la variable premier contient la valeur 2.3434 alors que la variable second contient la valeur -2.34. Nous avons donc stocké dans la mémoire de l’ordinateur les deux valeurs littérales. La figure 2.2 donne une illustration de l’écriture dans la mémoire des valeurs littérales. Il s’agit encore une fois de fournir un support pour la compréhension des programmes, ce n’est pas exactement de cette façon qu’une affectation est réalisée dans la mémoire de l’ordinateur.
...
De façon plus générale, l’affectation est l’opération qui consiste à stocker une valeur dans une variable (c’est-à-dire dans la mémoire de l’ordinateur). Dans un programme, le symbole qui représente l’opération d’affectation est le signe égal (=). L’instruction d’affectation s’écrit en donnant l’identificateur de la variable, suivi du symbole d’affectation, puis de la valeur et enfin d’un point virgule, c’est-à-dire (avec une valeur littérale) :
identificateur = valeur littérale ;
Pour que cette instruction soit correcte, c’est-à-dire acceptée par le compilateur, il faut que le type de la valeur littérale soit compatible avec celui de la variable. Il est par exemple impossible de placer un réel dans une variable de type entier car la méthode de stockage n’est pas la même. De façon générale, le seul moyen de placer une valeur littérale dans une variable est que cette valeur soit d’un type “plus petit” que celui la variable. Nous verrons dans la section 2.2.4 le sens à accorder à l’expression “plus petit”. Notons que la valeur précédente contenue dans la variable est perdue car elle est remplacée par la nouvelle valeur.
REMARQUE ✆
Entre les différents éléments d’une affectation, on peut mettre autant de fois le caractère d’espacement qu’on le souhaite. Ainsi toto=2 ; est-il aussi correct que toto = 2 ;. Comme nous l’avons déjà dit à la section 1.3.4, le compilateur ne fait pas la différence entre un espace et une combinaison d’espaces, de tabulations et de passage à la ligne.
Il faut bien comprendre qu’une variable ne peut stocker qu’une seul valeur, comme l’illustre l’exemple suivant :
Exemple 2.5 :
Considérons le programme suivant :
int x; x = 2; x = 3;
Quelle est la valeur contenue dans x après l’exécution de la troisième ligne? C’est bien entendu 3. En effet, le processeur exécute les instructions dans l’ordre du programme. Il commence par créer une variable, puis place la valeur 2 dans cette variable. Ensuite, il place la valeur 3 dans la même valeur. L’ancienne valeur est tout simplement remplacée par la nouvelle et n’existe donc plus.
2.2.3 Affectation du contenu d’une variable à une autre variable
L’affectation ne se contente pas de permettre de placer une valeur dans une variable. Elle permet aussi de recopier le contenu d’une variable (la valeur qu’elle contient) dans une autre variable.
Exemple 2.6 :
Pour une raison quelconque, nous souhaitons échanger les valeurs contenues dans deux variables distinctes. Voici comment nous allons procéder :
int premier, second, temporaire;
premier = 2;
second = 1;
temporaire = premier;
premier = second;
second = temporaire;
Les trois premières lignes déclarent les variables. Les deux suivantes affectent à ces variables leur valeurs initiales. Quand nous saurons demander à l’utilisateur de taper au clavier une valeur numérique (cf le chapitre 3), l’exemple deviendra moins artificiel, mais pour l’instant, cette présentation est indispensable.
Les lignes suivantes constituent la partie nouvelle. La première d’entre elles indique au processeur de placer dans la variable temporaire la valeur que contient la variable premier. De ce fait, après l’exécution de l’instruction, temporaire contient la valeur 2. Ensuite, l’instruction suivante indique au processeur de placer dans la variable premier la valeur contenue dans la variable second. Après cette instruction, la variable premier contient la valeur 1. Ceci n’a aucune incidence sur la variable temporaire qui est bien sûr indépendante de la variable premier. Elle contient donc toujours la valeur 2. La dernière instruction permet alors de placer la valeur contenue dans temporaire dans la variable second. A la fin du programme, la variable premier contient donc 1 et la variable second contient 2. On a bien réussi à échanger les valeurs contenues dans les deux variables. Tout ce processus est illustré par la figure 2.3.
Nous apprenons grâce à cet exemple deux choses :
REMARQUE
On utilisera souvent l’abus de langage qui consiste à dire la valeur d’une variable plutôt que la valeur du contenu d’une variable. En effet, le contenu d’une variable est un ensemble de bits qui est interprété par le processeur afin de donner une valeur.
2.2.4 Compatibilité des types Types numériques
Nous avons évité jusqu’à présent de considérer le problème suivant : que se passe-t-il si on tente de placer une valeur (ou le contenu d’une variable) de type float dans une variable de type double? En fait, le type double est plus précis que le type float, donc il serait naturel d’autoriser une telle conversion. C’est fort heureusement le cas. Cette opération de conversion s’appelle une promotion numérique. Il est toujours possible de placer un float dans un double, et de même, il est toujours possible de placer un entier de “petite taille” dans un entier plus grand (par exemple un entier de type int dans une variable de type long).
Il est aussi possible de placer n’importe quel entier dans une variable de type réel (float ou double) bien que ceci puisse entraîner une perte d’information. En effet, les entiers de type long possèdent 19 chiffres significatifs, alors que les float en comptent environ 8 et les double 15 (pour plus de précision, voir la section 2.4.2).
De façon générale, il est impossible de faire des affectations dans l’autre sens, comme par exemple placer le contenu d’une variable de type int dans une variable de type byte. Les types numériques
sont donc rangés dans l’ordre suivant :
byte<short<int<long<float<double
Quand un type est “plus petit” qu’un autre dans cet ordre, on dit qu’il est moins général que l’autre. Par exemple, int est moins général que float.
On peut toujours placer une valeur d’un type donnédans une variable d’un type plus général (ou égal) dans cet ordre. Il est en général impossible de placer une valeur d’un type donnédans une variable d’un type strictement moins général.
Autres types
Le type boolean est compatible seulement avec lui-même. Cela signifie qu’il est strictement impossible de placer une valeur de type boolean dans une variable d’un type autre que boolean. De la même façon, seule une valeur de type boolean peut être placée dans une variable de type boolean.
La situation des variables de type char est plus complexe et sera traitée à la section 2.5.3. Pour simplifier on peut dire que seule une valeur de type char peut être placée dans une variable de type char. Par contre, on peut placer une valeur de type char dans une variable de type int (et donc dans une variable d’un type “plus grand” que int).