Académique Documents
Professionnel Documents
Culture Documents
Premier programme
1.2 Programme
Il existe toutes sortes de programmes qui peuvent s’exécuter sur différents dispositifs pour rendre
un service. Nous n’allons pas essayer de définir ce qu’est un programme en général, mais présenter le
type de programmes que nous allons écrire dans ce cours.
Il s’agit de programmes applicatifs s’exécutant sur un ordinateur et composés d’une suite d’ins-
tructions. Le programme utilise des données dont certaines sont inscrites dans le programme, dans des
instructions et d’autres viennent de l’extérieur : elles sont tapées au clavier par l’utilisateur.
Les programmes auront des résultats qui s’afficheront à l’écran.
1
1.3. PROGRAMME SOURCE ET PROGRAMME CIBLE
CHAPITRE 1. PREMIER PROGRAMME
mots-clés et des noms qui ont un sens. Mais l’ordinateur ne sait pas exécuter directement ce texte. Le
programme qui s’exécute a une forme binaire : la fameuse suite de 0 et de 1 qui est copiée dans la
mémoire de l’ordinateur.
2 c
NFA031
CNAM 2016
CHAPITRE 1. PREMIER PROGRAMME 1.4. PREMIER PROGRAMME
Il existe d’autres outils professionnels comme par exemple Netbeans ou intelliJ. Vous pouvez les
utiliser si vous le souhaitez, mais ne vous fournirons pas d’aide pour eux.
Ce texte doit être tapé dans un fichier appelé Conversion.java. On n’a pas le choix du nom : ce nom
est celui qui apparaît après le mot-clé class sur la première ligne du programme, suivi de l’extension
.java.
— Tout programme Java est composé au minimum d’une classe (mot-clé class) dite principale,
qui elle même, contient une méthode de nom main. Les notions de classe et de méthode seront
abordées plus tard.
— public, class, static, void : sont des mots reservés c’est-à-dire qu’ils ont un sens particuliers
pour Java. On ne peut donc pas les utiliser comme nom pour des classes, des variables, etc...
— La classe principale : celle qui contient la méthode main. Elle est déclarée par
public class Nom_classe
C’est vous qui choisissez le nom de la classe.
Le code qui définit une classe est délimité par les caractères { et }
c
NFA031
CNAM 2016 3
1.4. PREMIER PROGRAMME CHAPITRE 1. PREMIER PROGRAMME
— Nom du programme : Le nom de la classe principale donne son nom au programme tout
entier et doit être également celui du fichier contenant le programme, complété de l’extension
.java
— La méthode main : obligatoire dans tout programme Java : c’est elle qui “commande” l’exé-
cution. Définie par une suite de déclarations et d’actions délimitées par { et }. Pour l’instant,
et jusqu’à ce que l’on sache définir des sous-programmes, c’est ici que vous écrirez vos pro-
grammes.
— Par convention, le nom d’une classe commence par une majuscule. Ce n’est pas une obligation
absolue, mais il est fortement conseillé de s’y conformer.
Autrement dit, pour écrire un programme que vous voulez appeler "Truc", ouvrez un fichier dans
un éditeur de texte, appelez ce fichier "Truc.java" ; Ecrivez dans ce fichier le squelette donné plus
haut, puis remplissez les ...
4 c
NFA031
CNAM 2016
CHAPITRE 1. PREMIER PROGRAMME
1.5. COMPILATION ET EXÉCUTION DU PROGRAMME
javac Conversion.java
Si le programme est correct, la commande se termine sans aucun affichage. Un fichier de code
intermédiaire ou byte-code a été créé, contenant le même programme sous forme binaire. Le fichier
s’appelle Conversion.class (même nom de fichier mais extension .class au lieu de .java).
Si le programme comporte des erreurs, c’est-à-dire s’il n’est pas écrit en java correct, le compila-
teur affiche des messages d’erreurs qui expliquent où sont les erreurs détectées et leur nature.
Dans un environnement de programmation intégré (IDE), pour compiler le programme il faut
actionner un bouton ou une option de menu. L’IDE appelle le compilateur et s’il y a des erreurs, il les
surligne dans l’éditeur et affiche les messages d’erreur.
Il affiche trois erreurs, mais en fait ces trois erreurs n’en sont qu’une : la première.
c
NFA031
CNAM 2016 5
1.6. RETOUR SUR LES INSTRUCTIONS CHAPITRE 1. PREMIER PROGRAMME
Le message affiche d’abord le fichier où se situe l’erreur (Bonjour.java). Dans notre exemple,
c’est évident, mais les gros programmes comportent plusieurs fichiers. Il est alors important de savoir
dans lequel est l’erreur. Ensuite, apparaît le numéro de ligne où l’erreur a été détectée (ici, 1, 2 et 4).
En troisième apparaît une courte explication en anglais (ici : class, interface, or enum expected). A la
ligne suivante apparaît le code de la ligne en question avec un petit chapeau qui marque l’emplacement
probable de l’erreur.
Corrigeons la première erreur et relançons la compilation.
C’est cette fois la seconde erreur qui est trouvée sur la ligne 3. Voyez que la petite marque est bien
sur le signe *.
Il arrive que le message d’erreur soit erroné : le compilateur n’a pas compris quelle était l’erreur.
Il arrive également que la position indiquée ne soit pas réellement celle de l’erreur. C’est l’endroit où
l’erreur a été détectée par le compilateur, mais l’erreur peut se situer avant : soit plus à gauche sur la
même ligne, soit sur une ligne au-dessus de cette position. Ainsi les messages d’erreur sont une aide
généralement pertinente pour détecter les erreurs mais cette aide n’est pas infaillible.
Avoir des erreurs de compilation lorsqu’on commence à programmer et même par la suite, est
tout-à-fait normal et ne doit pas décourager. Il faut corriger patiemment toutes les erreurs, jusqu’à
compiler le programme. Si vous êtes bloqué par une erreur que vous ne comprenez pas, demandez de
l’aide à vos enseignants.
1.5.3 Exécution
Pour exécuter le programme, il faut interpéter le code intermédiaire. C’est le rôle de la machine
virtuelle Java aussi appelée l’environnement d’exécution java (Java Runtime Environment-JRE). Cet
interpréteur peut être appelé en ligne de commande ou par un bouton ou une option de menu dans un
IDE.
En ligne de commande, il faut taper le nom de la commande java suivi du nom du programme
(ici : Conversion). Attention, ce n’est pas le nom d’un fichier qu’il faut donner, seulement le nom du
programme (Bonjour et non pas Bonjour.class).
6 c
NFA031
CNAM 2016
CHAPITRE 1. PREMIER PROGRAMME 1.7. LES VARIABLES
valeur n’est pas figée, il est possible de la changer lors de l’exécution du programme par une
affectation (cf. ci-dessous).
— des instructions d’entrée-sortie. Un programme a bien souvent (mais pas toujours) besoin d’in-
formations pour lui viennent de l’extérieur. Notre programme calcule la valeur en dollars d’une
somme en euros qui est donnée au moment de l’exécution par l’utilisateur. On a donc besoin
de dire qu’il faut faire entrer dans le programme une donnée par le biais du clavier. Il y a en
Java, comme dans tout autre langage de programmation, des instructions prédéfinies qui per-
mettent de faire cela (aller chercher une valeur au clavier, dans un fichier, sortir du programme
vers l’écran ou un fichier une valeur etc...).
— l’instruction d’affectation (=) qui permet de manipuler les variables déclarées. Elle permet de
mettre la valeur de ce qui est a droite de = dans la variable nommée à gauche du =.
1.7.1 déclaration
Nous avons déjà vu deux déclarations : double euros; et double dollars; Il est pos-
sible de déclarer les deux variables en une seule instruction double euros, dollars;. C’est
juste une abbréviation pour déclarer les deux variables sur la même ligne.
euros ou dollars sont les noms des variables et ont donc été choisis librement par l’auteur du
programme. Il y a cependant quelques contraintes dans le choix des symboles constituant les noms de
variables. Les voici :
— Les noms de variables sont des identificateurs qui peuvent contenir des lettres, des chiffres,
les signes souligné (tiret du 8) et dollar. Ces noms ne doivent pas commencer par un chiffre.
— Un nom de variable ne peut pas être un mot reservé : (abstract, boolean, if, public,
class, private, static, etc).
— Certains caractères ne peuvent pas apparaître dans un nom de variable : les espaces, les signes
de ponctuation et les caractères spéciaux.
— Dans un nom de variable, les majuscules et les minuscules sont significatifs. Par exemple
texte et TEXTE sont deux noms différents.
Exemples : a, id_a et X1 sont des noms de variables valides, alors que 1X et X-X ne le sont pas.
le type de la variable
Dans notre exemple de déclaration double euros;, le mot double est le nom d’un type
prédéfini en Java. Un type est un ensemble de valeurs particulières connues de la machine. Nous
c
NFA031
CNAM 2016 7
1.7. LES VARIABLES CHAPITRE 1. PREMIER PROGRAMME
verrons dans le prochain chapitre d’autres types : les nombres entiers, les caractères, les chaînes de
caractères, les booléens.
Ainsi, pour déclarer une variable, il faut donner un nom de type parmi int, double, char,
boolean ou String suivi d’un nom de variable que vous choisissez.
Pour déclarer plusieurs variables de même type en même temps, il faut donner un nom de type
suivi des noms de vos variables (séparés par des virgules).
C’est ce qu’on appelle la syntaxe Java des déclarations de variables. Il faut absolument se confor-
mer à cette règle pour déclarer des variables en Java, faute de quoi, votre code ne sera pas compris
par le compilateur et produira une erreur. Ainsi, double x; est correct alors que nombre x; ou
encore x double; seront rejetés.
nom_variable = expression ;
Attention : le symbole = utilisé dans une affectation ne désigne pas ici une égalité au sens mathé-
matique. Il faut lire cette ligne x reçoit 2.5 et non pas x est égal à 2.5. Ce n’est pas non plus un test
d’égalité pour savoir si x est égal ou non à 2.5.
Il est de bonne habitude de donner une valeur initiale à une variable dès qu’elle est déclarée. Java
nous permet d’affecter une valeur à une variable au moment de sa déclaration :
par exemple double x=2.5;
A droite de =, on peut mettre n’importe quelle valeur du bon type, y compris des variables. Comme
2.5 est une valeur du type double, notre exemple ne sera possible que si plus haut dans notre
programme, on a la déclaration double x;
8 c
NFA031
CNAM 2016
CHAPITRE 1. PREMIER PROGRAMME 1.8. LES APPELS DE MÉTHODES PRÉDÉFINIES
Les expressions
Les valeurs ne sont pas forcément simples, elles peuvent être le résultat d’un calcul. On pour-
rait par exemple écrire x=(18.3+20.1)*2.5;. On peut écrire l’expression (18.3+20.1)*2.5
car + et * sont des opérateurs connus dans le langage Java, désignant comme on s’y attend l’addition
et la multiplication et que ces opérateurs, appliqués à des nombres, calculent un nouveau nombre. En
effet, 18.3 + 20.1 donne 38.4 (un entier) et 38.4 × 2.5 donne 96.0 qui est un nombre à virgule.
Pour construire des expressions arithmétiques, c’est à dire des expressions dont le résultat est de
type int ou double on peut utiliser les opérateurs :+,-,*,/
Les lignes 3 et 4 déclarent les variables x et y. Elle sont donc connues dans la suite du programme.
Puis (ligne 5) la valeur 2.5 est donnée à y. Ensuite est calculé y+5.1. La valeur de y en ce point
de l’exécution est 2.5 donc y+5.1 vaut 7.6. A l’issue de l’exécution de la ligne 6, x vaut 7.6.
Finalement, (ligne 7), on évalue x+2.3. x à ce moment vaut 7.6 donc x+2.3 vaut 9.9. On donne
à x la valeur 9.9.
Cet exemple illustre le fait qu’une variable peut (puisque l’exécution d’un programme est séquen-
tielle) avoir plusieurs valeurs successives (d’où son nom de variable), et que l’on peut faire apparaître
une même variable à gauche et à droite d’une affectation : on met à jour son contenu en tenant compte
de la valeur qu’elle contient juste avant.
c
NFA031
CNAM 2016 9
1.8. LES APPELS DE MÉTHODES PRÉDÉFINIES CHAPITRE 1. PREMIER PROGRAMME
Un texte entre guillemets tel que "Somme en euros? " s’appelle une chaîne de caractères.
Pour fonctionner, cette méthode a besoin que l’utilisateur, lorsqu’il l’utilise, lui transmette une
information : la chaîne de caractères qu’il veut afficher à l’écran. Cette information transmise par
l’utilisateur de la méthode est ce qui se trouve entre les parenthèses qui suivent le nom de la méthode.
C’est ce qu’on appelle l’argument ou le paramètre de la méthode.
On a ici utilisé System.out.println pour afficher le message Somme en euros ? : en fai-
sant l’appel System.out.println("Somme en euros ? :) ;
Pour afficher coucou, il faut faire l’appel System.out.println("coucou").
Lors d’un appel, vous devez nécessairement transmettre une et une seule chaîne de caractères de
votre choix à System.out.println : c’est l’auteur de la méthode qui a fixé le nombre, le type et
l’ordre des arguments de cette méthode, lorsqu’il l’a écrite. Ainsi on ne pourra écrire :
System.out.println("coucou","bidule"). Cette ligne provoquerait une erreur de com-
pilation.
euros=Terminal.lireDouble();
10 c
NFA031
CNAM 2016
CHAPITRE 1. PREMIER PROGRAMME
1.9. LES MÉTHODES PRÉDÉFINIES D’ENTRÉE-SORTIE
Que se passera-t-il si l’utilisateur tape une ligne qui ne commence pas par un nombre entier ? Une
erreur se produira, et l’exécution du programme tout entier sera arrêtée. Nous verrons plus tard qu’il
y a un moyen de récupérer cette erreur pour relancer l’exécution.
La bibliothèque Terminal
La classe Terminal (écrite par nous), regroupe les principales méthodes de saisie et d’affichage
au terminal pour les types predéfinis que nous utiliserons dans ce cours : int, double, boolean,
char et String. Le fichier source Terminal.java, doit se trouver présent dans le même réper-
toire que vos programmes. Pour l’employer, il suffit de faire appel à la méthode qui vous intéresse
précédé du nom de la classe. Par exemple, Terminal.lireInt() renvoie le premier entier saisit
au clavier.
Terminal.lireType();
où Type est le nom du type que l’on souhaite saisir au clavier. La saisie se fait jusqu’à validation
par un changement de ligne. Voici la saisie d’un int dans x, d’un double dans y et d’un char
dans c :
int x;
double y;
char c; // Declarations
x = Terminal.lireInt();
y = Terminal.lireDouble();
c = Terminal.lireChar();
c
NFA031
CNAM 2016 11
1.9. LES MÉTHODES PRÉDÉFINIES D’ENTRÉE-SORTIE
CHAPITRE 1. PREMIER PROGRAMME
— Terminal.ecrireChar(n) ;
— Terminal.ecrireString(n) ;
— Terminal.ecrireIntln(n) ;
— Terminal.ecrireDoubleln(n) . . .
Ces méthodes d’affichage sont équivalentes à System.out.print et System.out.println.
Passer à la ligne
Il est possible de passer à la ligne sans rien afficher avec la méthode System.out.println()
sans paramètre ou avec la méthode Terminal.sautDeLigne().
12 c
NFA031
CNAM 2016
Chapitre 2
Expressions et algorithmes
L’exemple le plus typique d’expression est euros * 1.118. Il s’agit d’une expression arithmétique
qui donne le moyen de calculer un nombre à virgule. A l’exécution, ce calcul sera réalisé en tenant
compte de l’état courant de l’exécution et notamment, il utilisera la valeur de la variable euros à ce
moment là.
On voit que cette expression apparaît dans une instruction d’affectation qui va associer la valeur
calculée avec le nom dollars.
Il y a d’autres expressions dans le programme, notamment pour l’affectation de la ligne 6. Cette
expression est Terminal.lireDouble(). Elle est également le moyen de calculer une valeur de
type double, c’est-à-dire un nombre à virgule. Ce moyen de calcul consiste à aller chercher la valeur
tapée au clavier. Ce type d’expression s’appelle un appel de méthode.
Une autre sorte d’expression apparaît dans les commandes d’affichage : "Somme en euros? "
est une expression de type chaîne de caractères (String). Cette expression est un moyen de calcul tri-
1
2.2. TYPES, VALEURS ET OPÉRATEURS CHAPITRE 2. EXPRESSIONS ET ALGORITHMES
vial : sa valeur est la chaîne elle-même. Autrement dit, le calcul de "Somme en euros? " donne
la chaîne de caractères "Somme en euros? ".
Finalement, si l’on souligne toutes les expressions du programme, voici ce que cela donne.
public class Conversion {
public static void main (String[] args) {
double euros;
double dollars;
System.out.println("Somme en euros? ");
euros = Terminal.lireDouble();
dollars = euros *1.118;
System.out.println("La somme en dollars: ");
System.out.println(dollars);
}
}
Avant de passer en revue les différents types d’expressions, nous allons revenir sur les principaux
types de Java, ceux que nous allons utiliser dans ce cours, ainsi que sur les opérateurs que l’on peut
utiliser avec les valeurs de ces types.
2.1.1 Instructions
Une instruction est un élément de programme qui suffit à changer l’état de la mémoire ou de
l’écran de l’ordinateur. Nous en avons vu trois sortes jusqu’ici :
— les déclarations de variables changent la mémoire lors de l’exécution, car une case mémoire
est réservée pour la variable déclarée.
— l’affectation d’une valeur à une variable (=) va stocker la valeur dans la mémoire, dans la case
réservée à la variable
— les méthodes d’affichage telles que System.out.println changent l’état de l’écran.
Chacune de ces sortes d’instruction peut comporter une ou plusieurs expressions : une déclaration
de variable pour donner une valeur initiale, une affectation pour donner une valeur à la variable et un
appel de méthode pour préciser les paramètres donnés entre parenthèses.
2 c
NFA031
CNAM 2016
CHAPITRE 2. EXPRESSIONS ET ALGORITHMES 2.2. TYPES, VALEURS ET OPÉRATEURS
pas l’exactitude du résultat. Chaque calcul est vrai à un chouia près (les scientifiques préfèrent parler
d’epsilon, c’est la même idée).
Prenons un exemple concret.
public class Chouia{
public static void main(String[] args){
System.out.println(0.7*0.4);
}
}
Ce programme a pour but d’afficher le résultat de la multiplication 0.7*0.4. Le résultat exact est
0.28. L’exécution de ce programme affiche :
> java Chouia
0.27999999999999997
On voit donc que le résultat obtenu n’est pas le résultat exact mais qu’il en est très proche.
c
NFA031
CNAM 2016 3
2.3. LES DIFFÉRENTES SORTES D’EXPRESSION
CHAPITRE 2. EXPRESSIONS ET ALGORITHMES
4 c
NFA031
CNAM 2016
CHAPITRE 2. EXPRESSIONS ET ALGORITHMES
2.3. LES DIFFÉRENTES SORTES D’EXPRESSION
— un opérateur s’applique à une ou plusieurs expressions et constitue une expression. Par exemple
+ s’applique à deux nombres, par exemple à deux int et produit un int. 12+1 ou x+1 sont donc
des expressions. Pour que x+1 soit correct, il faut que la variable x ait le type int. x==1 est
une expression et son type est boolean, car c’est le type du résultat du calcul. Ce résultat est la
réponse à la question : x est-il égal à 1 ?
— une méthode qui renvoie un résultat est une expression. Par exemple, Terminal.lireInt() est une
expression de type int, car l’exécution de cette instruction est le moyen de calculer un int.
Certaines méthodes demandent des paramètres : les paramètres que l’on donne à une mé-
thode entre parenthèse sont des expressions. Prenons l’exemple de la méthode Math.min qui
renvoie le plus petit de deux nombres (soit deux entiers, soit deux double). Un appel à la mé-
thode doit comporter deux paramètres entre parenthèse qui sont des expressions. Par exemple
Math.min(x+1,17). Ou encore Math.min(x+1, Terminal.lireInt()). Ou encore
Math.min(Math.min(x,y),10).
Attention : certaines méthodes ne renvoient pas de résultat et ne sont donc pas des expressions.
C’est par exemple la cas de System.out.println et de Terminal.ecrireStringln. Ces méthodes ne peuvent
pas apparaître à droite d’une affectation et elles sont généralement seules sur une ligne de programme.
Exemples d’expressions
Pour savoir si une expression est correcte, il faut vérifier que chaque opérateur utilisé a le bon
nombre d’opérandes et que chaque appel de méthode a le bon nombre de paramètres. Ensuite, il faut
vérifier que les opérandes et paramètres ont les types attendus par l’opérateur ou la méthode.
Voyons des exemples d’expressions correctes.
1 + Terminal.lireInt()
Voyons d’abord l’appel de méthode : lireInt attend 0 paramètre, il y en a 0, donc l’appel est correct.
Voyons ensuite l’opérateur + : il existe plusieurs opérateurs + : celui des int, celui des double et celui
des String. Comme 1 est un int, on suppose qu’il s’agit de l’addition des entiers. Il lui faut deux
opérandes : il y en a bien deux, 1 et Terminal.lireInt(). Les deux sont de type int, l’expression est
correcte et le résultat est de type int.
(7<5) && true
L’opérateur < permet de comparer deux valeurs du même type. Ici, il y a bien deux opérandes de
type int. Cette partie de l’expression est donc correcte et calcule un booléen (en l’occurrence false,
puisque 7 n’est pas plus petit que 5). L’opérateur && attend deux opérandes booléens. Il y en a deux
et ils sont chacun du bon type : (7<5) et true. Le résultat de l’expression est un booléen.
Voici maintenant quelques exemples d’expressions incorrectes.
1 +
Il n’y a pas le bon nombre d’opérandes : il en faut deux, il n’y en a qu’un.
Math.min(x,y,12)
Il n’y a pas le bon nombre de paramètres dans l’appel de méthode.
Math.min(x<5,4)
Il y a le bon nombre de paramètres, mais le premier n’est pas du bon type : x<5 est une portion
d’expression correcte mais de type boolean alors que Math.min veut comparer deux nombres.
c
NFA031
CNAM 2016 5
2.4. ELÉMENTS DE CONCEPTION DES PROGRAMMES
CHAPITRE 2. EXPRESSIONS ET ALGORITHMES
6 c
NFA031
CNAM 2016
CHAPITRE 2. EXPRESSIONS ET ALGORITHMES
2.4. ELÉMENTS DE CONCEPTION DES PROGRAMMES
Math.min
min
int
c
NFA031
CNAM 2016 7
Chapitre 3
Conditionnelle et boucles
3.1 Introduction
Les instructions que nous avons vu jusqu’ici modifient la mémoire ou l’écran. Dans ce chapitre,
nous allons voir trois instructions qui ne modifient directement ni la mémoire ni l’écran mais qui
déterminent quelles instructions du programme vont s’exécuter.
On les appelle des instructions de contrôle et elles ont la particularité de contenir d’autres instruc-
tions. On va avoir des instructions imbriquées dans d’autres instructions. Ces instructions de contrôle
ont un rôle déterminant dans l’exécution des programmes et font que ceux-ci font plus que d’exécuter
une suite d’instructions invariable.
3.2 Conditionnelle
Nous voudrions maintenant écrire un programme qui, étant donné un prix Hors Taxe (HT) donné
par l’utilisateur, calcule et affiche le prix correspondant TTC. Il y a 2 taux possible de TVA : la TVA
normale à 20.0% et le taux réduit à 5.5%. On va demander aussi à l’utilisateur la catégorie du taux
qu’il faut appliquer.
données
— entrée : un prix HT de type double (pHT), un taux de type int (t) (0 pour normal et 1 pour
réduit)
— sortie : un prix TTC de type double (pTTC)
algorithme
1. afficher un message demandant une somme HT à l’utilisateur.
2. recueillir la réponse dans pHT
3. afficher un message demandant le taux (0 ou 1).
4. recueillir la réponse dans t
5. 2 cas :
(a) Cas 1 :le taux demandé est normal pTTC= pHT+ (pHT*0.2)
(b) Cas 2 :le taux demandé est réduit pTTC= pHT+ (pHT*0.055)
6. afficher pTTC
1
3.2. CONDITIONNELLE CHAPITRE 3. CONDITIONNELLE ET BOUCLES
Avec ce que nous connaissons déjà en Java, nous ne pouvons pas coder cet algorithme. La ligne
5 pose problème : elle dit qu’il faut dans un certain cas exécuter une tâche, et dans l’autre exécuter
une autre tâche. Nous ne pouvons pas exprimer cela en Java pour l’instant, car nous ne savons qu’exé-
cuter, les unes après les autres de façon inconditionnelle la suite d’instructions qui constitue notre
programme.
Pour faire cela, il y a une instruction particulière en Java, comme dans tout autre langage de
programmation : l’instruction conditionnelle, qui à la forme suivante :
if (condition) {
instructions1
}else{
instructions2
}
et s’exécute de la façon suivante : si la condition est vraie c’est la suite d’instructions instructions1
qui est exécutée ; si la condition est fausse, c’est la suite d’instructions instructions2 qui est exé-
cutée.
La condition doit être une expression booléenne c’est à dire une expression dont la valeur est soit
true soit false.
Voilà le programme Java utilisant une conditionnelle qui calcule le prix TTC :
double pHT,pTTC;
int t;
Terminal.ecrireString("Entrer le prix HT: ");
pHT = Terminal.lireDouble();
2 c
NFA031
CNAM 2016
CHAPITRE 3. CONDITIONNELLE ET BOUCLES 3.3. ITÉRATION
if (condition1){s1}
else if (condition2) {s2}
...
else if (conditionp) {sp}
else {sf}
On peut mettre autant de else if que l’on veut, mais 0 ou 1 else (et toujours à la fin). else if
signifie sinon si. Il en découle que les conditions sont évaluées dans l’ordre. La première qui est vraie
donne lieu à l’exécution de la séquence d’instructions qui est y associée. Les conditions qui suivent
sont ignorées même si elle sont vraies. Elles ne sont même pas calculées. Si aucune condition n’est
vraie, c’est le else, s’il y en a un, qui est exécuté. Si aucune condition n’est vraie et qu’il n’y a pas
de else, l’instruction if n’exécute aucune instruction.
3.3 Itération
Ecrivons un programme qui affiche à l’écran un rectangle formé de 5 lignes de 4 étoiles. Ceci est
facile : il suffit de faire 5 appels consécutifs à la méthode Terminal.ecrireStringln
public class Rectangle {
public static void main (String[] args) {
Terminal.ecrireStringln("****");
Terminal.ecrireStringln("****");
Terminal.ecrireStringln("****");
Terminal.ecrireStringln("****");
Terminal.ecrireStringln("****");
}
}
c
NFA031
CNAM 2016 3
3.3. ITÉRATION CHAPITRE 3. CONDITIONNELLE ET BOUCLES
Ceci est possible, mais ce n’est pas très élégant ! Nous avons des instructions qui nous permettent
de répéter des tâches. Elles s’appellent les instructions d’itération.
Répète 5 fois, pour i allant de 0 à 4, les instructions qui sont dans les accolades, c’est à dire dans
notre cas : afficher une ligne de 4 *.
Cela revient bien à dire : répète 5 fois "afficher une ligne de 4 étoiles".
Détaillons cela : La boucle for fonctionne avec un compteur du nombre de répétitions qui est
géré dans les 3 expressions entre les parenthèses.
— La première :int i=0 donne un nom à ce compteur (i) et lui donne une valeur initiale 0.
Ce compteur ne sera connu qu’à l’intérieur de la boucle for. (il a été declaré dans la boucle for
int i)
— La troisième : i=i+1 dit que les valeurs successives de i seront 0 puis 0+1 puis 1+1, puis
2+1 etc.
— La seconde (i<5) dit quand s’arrête l’énumération des valeurs de i : la première fois que i<5
est faux.
Grâce à ces 3 informations nous savons que i va prendre les valeurs successives 0, 1, 2, 3, 4. Pour
chacune de ces valeurs successives, on répétera les instructions dans les accolades.
Jouons un peu
Pour être sur d’avoir compris, examinons les programmes suivants :
public class Rectangle {
public static void main (String[] args) {
for (int i=0;i<5;i=i+2){
Terminal.ecrireStringln("****");
}
}
}
4 c
NFA031
CNAM 2016
CHAPITRE 3. CONDITIONNELLE ET BOUCLES 3.3. ITÉRATION
i ne prendra aucune valeur car sa première valeur (1) n’est pas égale à 5. Les instructions entre
accolades ne sont jamais exécutées. Ce programme n’affiche rien.
La variable ligne est déclarée dans la première instruction. Elle a une valeur à l’issue de la
troisième instruction. On peut donc tout à fait consulter sa valeur dans la quatrième instruction (le
for). Si l’utilisateur entre 5 au clavier, il y aura 5 étapes et notre programme affichera 5 lignes de 4
étoiles. Si l’utilisateur entre 8 au clavier, il y aura 8 étapes et notre programme affichera 8 lignes de 4
étoiles.
c
NFA031
CNAM 2016 5
3.3. ITÉRATION CHAPITRE 3. CONDITIONNELLE ET BOUCLES
Le compteur d’étapes est connu dans la boucle. On peut tout à fait consulter son contenu dans la
boucle.
public class Rectangle3 {
public static void main (String[] args) {
int lignes;
Terminal.ecrireString("combien de lignes d’etoiles ?:");
lignes=Terminal.lireInt();
for (int i=1;i<=lignes;i=i+1){
Terminal.ecrireInt(i);
Terminal.ecrireStringln("****");
}
}
}
Et voici un programme qui affiche les 10 premiers entiers en partant du plus grand vers le plus
petit :
public class PremEntiers {
public static void main (String[] args) {
for (int i=10;i>0;i=i-1){
Terminal.ecrireString(i + ",");
}
}
}
La syntaxe de la boucle for en Java est beaucoup plus permissive que ce qui à été exposé ici.
Nous nous sommes contentés de décrire son utilisation usuelle. Nous reviendrons plus en détail sur
les boucles dans un chapitre ultérieur.
6 c
NFA031
CNAM 2016
CHAPITRE 3. CONDITIONNELLE ET BOUCLES 3.3. ITÉRATION
while (condition) {
instructions
}
Cette boucle signifie : tant que la condition est vraie, exécuter les instructions entre les accolades
(le corps de la boucle). Grâce à cette boucle, on peut répéter une tâche tant qu’un événement dans le
corps de la boucle ne s’est pas produit. C’est exactement ce qu’il nous faut : nous devons répéter la
demande du taux, tant que l’utilisateur n’a pas fourni une réponse correcte.
écriture de la boucle
La condition de notre boucle devra être une expression booléenne Java qui exprime le fait que la
réponse de l’utilisateur est correcte. Pour faire cela, il suffit d’ajouter une variable à notre programme,
que nous appellerons testReponse. Notre programme doit s’arranger pour qu’elle ait la valeur
true dès que la dernière saisie de l’utilisateur est correcte, c’est à dire si t vaut 0 ou 1, et false
sinon.
Notre boucle pourra s’écrire :
while (testReponse==false){
Terminal.ecrireStringln("Entrer taux (normal->0 reduit ->1) ");
t = Terminal.lireInt();
if (t==0 || t==1){
testReponse=true;
}
else {
testReponse=false;
}
}
comportement de la boucle
La condition de la boucle est testée avant chaque exécution du corps de la boucle. On commence
donc par tester la condition ; si elle est vraie le corps est exécuté une fois, puis on teste à nouveau la
c
NFA031
CNAM 2016 7
3.3. ITÉRATION CHAPITRE 3. CONDITIONNELLE ET BOUCLES
condition et ainsi de suite. L’exécution de la boucle se termine la première fois que la condition est
fausse.
initialisation de la boucle
Puisque testReponse==false est la première chose exécutée lorsque la boucle est exécutée,
il faut donc que testReponse ait une valeur avant l’entrée dans la boucle. C’est ce qu’on appelle
l’initialisation de la boucle. ici, puisque l’on veut entrer au moins une fois dans la boucle, il faut
initialiser testReponse avec false.
double pHT,pTTC;
int t=0;
boolean testReponse=false;
Terminal.ecrireString("Entrer le prix HT: ");
pHT = Terminal.lireDouble();
while (testReponse==false){
Terminal.ecrireString("Entrer taux (normal->0 reduit ->1) ");
t = Terminal.lireInt();
if (t==0 || t==1){
testReponse=true;
}
else {
testReponse=false;
}
}
if (t==0){
pTTC=pHT + (pHT*0.2);
}
else {
pTTC=pHT + (pHT*0.055);
}
Terminal.ecrireStringln("La somme TTC: "+ pTTC );
}
}
8 c
NFA031
CNAM 2016
CHAPITRE 3. CONDITIONNELLE ET BOUCLES 3.3. ITÉRATION
Pour qu’une boucle while termine, il faut s’assurer que le corps de la boucle contient des ins-
tructions qui mettent à jour la condition de boucle. Autrement dit, le corps de la boucle est toujours
constitué de deux morceaux :
— le morceau décrivant la tâche à effectuer à chaque étape
— le morceau décrivant la mise à jour de la condition de sortie
Pour qu’une boucle termine toujours, il faut que chaque mise à jour de la condition de sortie,
nous rapproche du moment où la condition sera fausse.
C’est bien le cas dans l’exemple suivant :
public class b1 {
public static void main (String[] args) {
int i=1;
while (i!=10){
Terminal.ecrireString(i + ",");
i=i+1;
}
}
}
int i=1;
while (i!=10){
Terminal.ecrireString(i + ",");
i=i+2;
}
}
}
La boucle while et la boucle for sont d’une certaine façon équivalentes : on peut toujours transfor-
mer une boucle for en un while équivalent et réciproquement. Mais dans certain cas, c’est l’une qui
est plus facile à écrire et dans d’autres, c’est l’autre.
c
NFA031
CNAM 2016 9
3.3. ITÉRATION CHAPITRE 3. CONDITIONNELLE ET BOUCLES
Nous vous proposons le critère suivant pour déterminer quelle boucle choisir : si au moment où
la boucle commence, on connait le nombre de tours à effectuer, choisir un for. Dans les autres cas,
choisir un while.
10 c
NFA031
CNAM 2016
Chapitre 4
Comment programmer
Nous venons de voir dans le chapitre précédent un ensemble d’instructions : l’affectation, la condi-
tionnelle, le boucle for, la boucle while. S’il est relativement aisé de comprendre un code Java à ce
point du cours, il reste une grosse difficulté : concevoir un programme. Quelles variables faut-il défi-
nir ? Quelles instructions faut-il employer ? Comment les assembler ?
Dans ce chapitre, nous vous présentons une démarche pour répondre à ces questions et vous aider
à concevoir un programme.
Exemple
Nous allons utiliser tout au long de ce chapitre un exemple. Il est volontairement choisi un peu
complexe par rapport à notre niveau actuel pour, d’une part, couvrir l’ensemble de la démarche et,
1
4.1. CONSTRUCTION DU PROGRAMME CHAPITRE 4. COMMENT PROGRAMMER
d’autre part, vous montrer qu’une approche méthodique permet de résoudre des problèmes assez com-
plexes.
Son énoncé est le suivant :
Bien entendu, vous pouvez remplacer n et p par n’importe quelle valeur selon vos besoins.
Explications : Math.random () est une méthode faisant partie de l’API Java. Elle retourne
un double aléatoire entre 0, borne comprise, et 1, borne non comprise. En multipliant son résultat
par n on obtient un double dans l’intervalle 0..n, 0 compris et n non compris. On convertit ce
résultat en int ce qui a pour effet de tronquer le nombre à la virgule sans l’arrondir. En ajoutant p
au résultat, on obtient un nombre compris entre p et n-1+p.
Ensuite, la méthode Thread.sleep(long temps) permet d’interrompre momentanément
l’exécution du programme. Le programme reprendra son exécution au bout de temps millisecondes.
Par exemple,
Thread.sleep(2000);
Que faut-il donner au programme comme données pour faire le calcul ? En lisant l’énoncé, il y
a un certain nombre de données. D’une part le nombre de messages à afficher qui doit être lu au
clavier. Nous prendrons un int : nbMessages. En ce qui concerne les messages, ils seront définis
directement dans le code Java.
Quel est le résultat attendu ? Le résultat attendu est une suite de messages affichés à l’écran. Autant
que l’utilisateur en a demandé. Chacun de ces messages sera suivi par des points et la ligne sera
terminée par le mot Fait. Pour tout ceci, il s’agit de valeurs prédéfinies dans le programme et qui
seront affichées directement. Il n’y a pas nécessité de définir des variables.
Exemple
Est-ce que le problème est connu ? Sur l’exemple abordé, il ne s’agit pas vraiment d’un problème
bien connu, mais il reste relativement simple à comprendre.
Construisons un jeu d’exemples. Choisissons une liste de messages dans un premier temps. Nous
en prenons 6 par exemple :
— "Reformatage du disque dur."
— "Effacement de tous les fichiers."
— "Destruction de la mémoire."
— "Vidage de la base de registres."
— "Recopie du virus dans tous les fichiers."
— "Envoi du virus à tous vos contacts."
Nous allons construire maintenant un tableau avec l’entrée nbMessages et un exemple de ré-
sultat attendu
Certes avec seulement 6 messages différents, il y a de fortes chances que le même message sorte
plusieurs fois au cours de la même exécution du programme, mais en en augmentant le nombre,
cela arrivera moins fréquemment. Pour en augmenter le nombre il faudrait utiliser une structure de
données que nous n’avons pas encore abordée. Nous laisserons cette seconde solution pour plus tard...
Nous nous contenterons alors de ces 6 messages différents.
Exemple
Le seul cas particulier est celui où l’utilisateur entre une valeur plus petite ou égale à 0. Dans ce
cas, on peut faire plusieurs choix :
— le programme affiche une erreur et sort sans rien écrire d’autre ;
— le programme affiche une erreur et redemande une valeur correcte ;
— le programme ne fait rien.
D’autres choix sont encore possibles, mais nous ne pourrons les envisager que plus tard. Pour des
raisons de simplification, ici, nous allons prendre la troisième option.
La conditionnelle
L’instruction conditionnelle permet d’exécuter des codes spécifiques en fonction d’une ou plu-
sieurs conditions. Parfois on entend parler de "boucle" conditionnelle mais ce terme est à bannir
au plus vite car il est inapproprié et est source de confusion. En effet, dans le mot boucle, il y a
la notion de répétition du même calcul, opération pour laquelle l’instruction conditionnelle n’est
absolument pas conçue. En effet, le if permet d’effectuer un code spécifique en fonction d’une ou
plusieurs conditions mais ne permet en aucun cas de répéter plusieurs fois ce code. Pour ces raisons,
nous n’emploierons que l’expression instruction conditionnelle ou nous parlerons du if.
Quand faut-il penser à l’instruction conditionnelle ? il faut y penser dès que, suivant une ou
plusieurs conditions, il y a une ou plusieurs actions spécifiques à réaliser. Cependant, il ne faut pas
confondre avec une boucle while où il y a aussi une condition mais elle est associée avec une notion
de répétition du calcul. Ici, pas de notion de reprise du calcul.
Il y a essentiellement 4 formes possibles et toutes ces formes sont envisageables. Juste différents
cas avec des calculs spécifiques. Il suffit de choisir celle qui est adaptée à votre problème :
un cas particulier et rien d’autre
Cette première forme se détecte lorsque nous sommes amenés à dire si ... alors ... sans rien
préciser d’autre. Pas de sinon ou, au mieux, un sinon rien. Un if sans else conviendra. Ceci
nous donne un squelette Java de la forme :
if (condition) {
instructions
}
Si cela ne suffit pas à formuler votre problème, envisagez une des autres formes ci-dessous.
un calcul spécifique et un cas général ou un cas par défaut
Cette deuxième forme est à adopter si votre problème peut s’exprimer de la forme si ... alors
... sinon .... Un if avec cette fois-ci un else conviendra. Soit un squelette Java de la forme :
if (condition) {
instructions
} else {
instructions
}
devrait convenir. Si ce n’est pas le cas, envisagez une des autres formes.
plusieurs cas spécifiques sans cas général ni cas par défaut
Cette troisième forme s’énonce par si ... alors ... sinon si ... ... avec un ou plusieurs sinon si mais
pas de sinon final. Un if avec un ou plusieurs else if, autant que nécessaire, conviendra.
Soit un squelette de la forme :
if (condition) {
instructions
} else if (condition) {
instructions
} ...
devrait convenir avec autant de if apparaissant que de cas différents à envisager. Si ce n’est
pas le cas, envisagez une autre forme.
plusieurs cas spécifiques avec un cas général ou un cas par défaut
Cette dernière forme s’énonce par si ... alors ... sinon si ... ... sinon ... avec plusieurs sinon si
et un sinon final. Un if avec un ou plusieurs else if puis un else final conviendra. Soit
un squelette de la forme :
if (condition) {
instructions
} else if (condition) {
instructions
} else if ... {
...
} else {
instructions
}
devrait convenir avec, comme pour le cas précédent, autant de else if que nécessaire. Le
else est toujours en dernier. Si ce n’est pas le cas et si aucune des formes précédentes ne
convient, posez-vous la question de savoir si c’est bien une conditionnelle qu’il vous faut.
Exemple
Reprenons notre exemple. À ce point de notre progression sur le problème posé, il est temps de
se poser la question de savoir si une ou plusieurs conditionnelles sont nécessaires. En reprenant
l’énoncé, on peut y lire :
Chacun de ces messages sera pris au hasard dans une liste.
Par ailleurs, nous avons établi une liste de 6 messages comme il nous a été demandé, et il nous a été
expliqué comment tirer un nombre au hasard par l’instruction suivante :
Avec ces deux éléments, il nous est possible de générer un nombre compris entre 0 et 5 avec n
valant 6 et p valant 0, puis, si alea vaut 0 on affiche le premier message, sinon si alea vaut 1 on
affiche le deuxième et ainsi de suite jusqu’au cinquième message (alea valant 4 dans ce dernier cas)
sinon on affiche le sixième. Ceci correspond à la quatrième forme présentée ci-dessus. On peut d’ores
et déjà affirmer qu’il nous faudra une instruction conditionnelle et exprimer cette partie du problème
sous la forme du pseudo-code qui suit.
Les boucles
Une fois que nous avons déterminé s’il y a une ou plusieurs instructions conditionnelles dans le
programme, il est temps de se poser la question de savoir s’il y a une ou plusieurs boucles puis de
savoir de quel type de boucles nous aurons besoin.
Ce qu’il faut regarder pour répondre à cette question, c’est s’il y a une notion de répétition d’ac-
tions ou un ensemble de données sur lequel effectuer certaines actions. Si, en décrivant la solution,
on entend des termes comme pour tout, pour chaque, faire n fois, pour ... allant de ... à ..., répéter
jusqu’à, répéter tant que, refaire, reprendre, etc., nous pouvons nous attendre à avoir une ou plusieurs
boucles dans le programme. Ce sont ces indicateurs qui vont nous inciter à mettre une boucle et dans
un second temps, en fonction des termes employés, à choisir le type de boucle.
Exemple
Reprenons notre exemple et essayons de voir s’il y a une ou plusieurs boucles. Commençons par
relire l’énoncé.
Ce programme demandera à l’utilisateur un entier : le nombre de fois qu’il devra écrire
un message à l’écran.
A priori, il va falloir afficher un message à l’écran un certain nombre de fois. Le nombre de fois est
variable d’une exécution à l’autre, il faudra alors impérativement une boucle. Continuons la lecture.
Chacun de ces messages sera pris au hasard dans une liste.
Nous avons déjà traité cette partie. Elle a donné lieu à l’introduction d’une conditionnelle. Il n’y a
pas vraiment de notion d’itération ici sauf qu’il faudra le faire pour chacun des messages à afficher.
Un élément à garder en mémoire pour plus tard quand nous agencerons les instructions...
Sur la même ligne que le message et à sa suite, pour simuler la visualisation de l’avance-
ment de l’opération en cours, le programme affichera à l’écran un nouveau point toutes
les demi-secondes entre 5 et 15 fois.
De nouveau une action à répéter un certain nombre de fois. Faut-il une deuxième boucle ou la précé-
dente suffira ? Apparemment, cette répétition n’est pas la même que la précédente. La seule relation
avec la précédente est qu’il faudra le faire pour chacun des messages mais notons cela pour l’instant.
Il sera temps de s’en préoccuper quand nous organiserons les instructions. Pour l’instant, écrivons
qu’il nous faudra une seconde boucle. Continuons la lecture de l’énoncé.
Le nombre de fois sera aussi pris au hasard. Ensuite, il affichera "Fait" avant de passer
au message suivant.
On parle de nouveau d’un nombre de fois... Mais ! Attention ! Il s’agit du nombre de fois qu’il faut
afficher un point, action que nous avons déjà traitée. Donc, pas de nouvelle boucle cette fois-ci.
Ensuite, il ne s’agit que d’afficher un texte à l’écran. Pas de nouvelle boucle non plus, même s’il
faudra le faire pour chaque message. La boucle sur les messages permettra de répéter cet affichage.
Dans la suite de l’énoncé, nous ne trouvons que quelques conseils ou indications pour résoudre
certains sous-problèmes. En vérifiant bien, il n’y a pas de nouvelle notion de boucle de toute évidence.
En résumé, il y aura une conditionnelle et deux boucles. Nous avons à notre disposition deux types
de boucles avec une variante pour la boucle tant que. Il reste à déterminer de quel type sera chacune
des boucles.
— une boucle while quand le nombre de tours n’est pas calculable à l’entrée de la boucle. Dans
ce second cas, il y a toujours une condition associée à la boucle : soit une condition d’arrêt
(répéter jusqu’à) soit une condition de reprise (répéter tant que).
Un autre critère de choix équivalent :
— quand on utilise des termes de la forme pour tout, pour chaque, faire n fois, pour ... allant de
... à ..., répéter ... fois, refaire ... fois, pour décrire la boucle, on choisira une boucle for ;
— quand ce sont plutôt des termes contenant tant que ou jusqu’à suivis d’une condition, on choi-
sira une boucle while. Après, un while ou un do ... while ? Pour l’instant, nous ne chercherons
pas à les différencier. Votre future expérience vous guidera dans votre choix.
Exemple
Toujours sur notre exemple de faux virus, nous avons deux boucles.
La première consiste à afficher du texte un certain nombre de fois. Au moment où nous concevons
le programme, nous ne connaissons pas ce nombre de fois puisqu’il ne sera précisé par l’utilisateur
qu’au moment de l’exécution. Notons que, quelque soit le problème, c’est presque toujours le cas :
on ne connait pas le nombre de tours de boucle à faire au moment de la conception du programme.
Par contre, au moment de d’entrer dans la boucle, l’utilisateur aura indiqué combien de messages à
écrire. C’est ce moment là qui est important. Il faudra alors préférer une boucle for.
La seconde consiste à afficher un certain nombre de points avec une temporisation entre deux
points. Ce nombre n’est pas connu maintenant mais le sera au moment d’entrer dans la boucle. Donc,
là-aussi une boucle for.
Cas de deux conditionnelles Ce cas ne pose pas de gros problèmes en général. Il est facile de voir
si l’une doit être avant, après ou dans l’autre. Tout de même, dans tous les cas, demandez-vous s’il ne
serait pas possible de n’en faire qu’une. Le code obtenu n’en sera que plus simple.
Cas d’une conditionnelle et d’une boucle Si nous avons une conditionnelle et une boucle, quel
que soit le type de la boucle, la conditionnelle peut être placée avant, après ou dans la boucle, ou,
encore, elle peut contenir la boucle. Notez que cette dernière solution peut amener à reproduire la
boucle plusieurs fois. Il faut essayer d’éviter au maximum ce dernier choix en utilisant au besoin
des variables locales pour essayer de transformer la conditionnelle et de la placer avant la boucle.
Cependant, ceci n’est pas toujours faisable.
Pour déterminer quel agencement convient le mieux, il faut se poser les questions suivantes :
— Est-ce que la valeur de la ou des conditions du if est susceptible de varier au fil des tours de
boucle et est-ce qu’en conséquence l’ensemble des instructions à exécuter peut varier à chaque
tour de la boucle ?
— si oui, placer le if dans la boucle
— si non passer à la question suivante
— Est-ce que la condition porte sur le résultat final de la boucle ?
— si oui, placer le if après la boucle
— si non passer à la question suivante
— Est-ce que le calcul à faire dans la boucle varie en fonction de la condition mais ne change pas
d’un tour à l’autre ?
— Est-ce qu’il ne s’agit que d’une ou plusieurs valeurs utilisées dans le calcul qui changent
mais pas le calcul en lui-même (calcul paramétré) ?
— si oui, pour chaque paramètre du calcul, utiliser une variable, placer le if avant la
boucle et y initialiser ses paramètres.
— si non passer à la question suivante
— Est-ce qu’il est nécessaire d’optimiser le temps de calcul à tout prix (ce cas est rare) ?
— si oui, mettre la boucle dans la conditionnelle en la répétant dans chaque cas aux mo-
difications nécessaires près.
— si non placer le if dans la boucle.
Cas de deux boucles Dans le cas de deux boucles, il n’y a que deux possibilités : l’une après l’autre
ou l’une dans l’autre. Pour savoir laquelle des deux options prendre, il suffit de regarder si l’une
d’entre-elles doit être effectuée pour chaque tour de l’autre. Si tel est le cas, la première devra être
placée à l’intérieur de la seconde. Dans le cas contraire, on essaiera de les placer consécutivement.
Exemple
Reprenons notre exemple. Nous avons une conditionnelle et deux boucles for. Comment les as-
sembler ? Notons que le type de la boucle importe peu dans cette étape.
Nous avons une première boucle for sur le nombre de messages à afficher et une condition sur
une variable alea permet de choisir le message. De toute évidence, si on veut que le message change
à chaque fois, il va falloir tirer un nouveau nombre alea au hasard pour chaque affichage de message
et, selon le résultat obtenu, afficher le message correspondant. C’est le premier cas. Donc, le if sera
dans cette boucle for.
Nous avons toujours la première boucle for sur le nombre de messages à afficher et la boucle
d’affichage des points avec temporisation. Là encore, nous avons noté qu’il faut afficher ces points
pour chaque affichage de message. Ainsi la boucle for sur les points se situera dans la boucle for
sur les messages. Ainsi, la boucle sur les messages contiendra à la fois un if et un for. Mais
comment organiser ces deux instructions ?
En regardant plus précisément, il faut d’abord afficher le message tiré au hasard, ensuite les points
et, enfin, afficher le mot "Fait" en passant ensuite à la ligne. De là, nous en déduisons que le if doit
être placé avant le for et qu’il y aura encore un affichage à faire à la suite du for. Nous en arrivons
à la structure de programme suivante qui n’était pas forcément évidente à la lecture de l’énoncé :
lecture du nombre de messages
for sur les messages {
calcul de la variable alea
Exemple
A l’étape 1, nous avons déterminé qu’il n’y a qu’une seule entrée : le nombre de messages nommé
nbMessages de type int. Dans cette même étape, nous aurons en sortie une suite de messages
sélectionnés dans un ensemble. Il n’y aura pas besoin de variable particulière pour ces messages.
Nous avons construit un jeu d’exemple lors de l’étape 2 et déterminé le comportement du programme
pour les cas particuliers.
A l’étape 4, nous avons déterminé que nous tirons au hasard un entier pour choisir un des mes-
sages. Le résultat est mémorisé dans une variable alea qui devra être de type int. Dans la même
étape, plus loin, nous avons une première boucle for sur le nombre de messages. Il nous faudra
une variable aussi. Appelons la n. Elle sera aussi de type int et sera déclarée dans la boucle for.
Ensuite, il nous faudra aussi tirer au hasard un nombre entre 5 et 15. Il nous faut de nouveau une va-
riable de type int pour mémoriser ce nombre. Nous l’appellerons tempo. un nouvel indice de boucle
que nous appellerons k de type int sera nécessaire pour la seconde boucle for. Celle comptant le
nombre de points à afficher. Comme pour n, cette variable sera déclarée dans la boucle.
Maintenant nous pouvons formaliser l’algorithme :
Entrée
nbMessages un entier : le nombre de messages à afficher
Sortie
autant de messages que demandé
Variables locales
alea un entier : tirage au sort du message
n un entier : compteur du nombre de messages affichés
tempo un entier : nombre de points à afficher
k un entier : compteur du nombre de points affichés
Début
écrire "Nombre de messages : "
lire nbMessages
Une remarque : lors de l’étape 3 nous avons identifié le cas où l’utilisateur entre une valeur
négative ou nulle pour nbMessages. Nous avons choisi de ne rien faire dans cette éventualité.
Dans tous les langages de programmation, le pour tout n allant de 1 à nbMessages
se traduira par une boucle for qui, dans le cas qui nous intéresse, sort immédiatement sans jamais
exécuter son corps. C’est ce que nous avions prévu comme comportement !
traduction, il faut veiller à ce que le programme reflète au maximum l’algorithme. Il ne doit pas y
avoir de "prise d’initiative" lors de cette phase. Chaque ligne du programme doit correspondre à une
ligne de l’algorithme. Ceci afin de faciliter le débogage du code par vous ou tout autre personne
ultérieurement.
Exemple
Nous avons pris soin d’écrire l’algorithme le plus précisément possible afin qu’il reste facilement
compréhensible par une autre personne tout en collant au mieux au jeu d’instructions des langages
de programmation modernes issus de l’impératif. Il en résulte que la traduction se fera presque mot
à mot. Voici le code obtenu où l’algorithme a été mis en commentaire pour mettre en évidence cette
similarité.
public static void main(String[] args) throws InterruptedException {
// Entrée
// nbMessages un entier : le nombre de messages à afficher
int nbMessages;
// Sortie
// autant de messages que demandé
// *********pas de ligne de code correspondant
// Variables locales
// alea un entier : tirage au sort du message
int alea;
// Début
// écrire "Nombre de messages : "
System.out.print("Nombre de messages : ");
// lire nbMessages
nbMessages = Terminal.lireInt();
// si alea == 0
if (alea == 0) {
// écrire "Je reformate le disque dur."
System.out.print("Je reformate le disque dur.");
// sinon si alea == 1
} else if (alea == 1) {
// écrire "Effacement de tous les fichiers."
System.out.print("Effacement de tous les fichiers.");
// sinon si alea == 2
} else if (alea == 2) {
// écrire "Destruction de la mémoire."
System.out.print("Destruction de la mémoire.");
// sinon si alea == 3
} else if (alea == 3) {
// écrire "Vidage de la base de registres."
System.out.print("Vidage de la base de registres.");
// sinon si alea == 4
} else if (alea == 4) {
// écrire "Recopie du virus dans tous les fichiers."
System.out.print(
"Recopie du virus dans tous les fichiers.");
// sinon
} else {
// écrire "Envoi du virus à tous vos contacts."
System.out.print("Envoi du virus à tous vos contacts.");
// fin si
}
4.2 En résumé
Lors de la réalisation d’un exercice de programmation, il est tentant d’écrire directement le code
dans le langage de programmation choisi. Or, un débutant aura beaucoup de mal à aboutir de la sorte
à un programme opérationnel. On a des difficultés à comprendre ou à imaginer la méthode ; on ne sait
pas par quel bout commencer ; on mélange un peu tout ; etc. C’est pourquoi il vaut mieux procéder
méthodiquement même si la démarche proposée peut sembler lourde. Après, au fil de l’acquisition
d’expérience, les différentes étapes deviendront de plus en plus faciles et même, vous arriverez à
coder des problèmes de moins en moins simples directement en Java.
La démarche est la suivante :
Entrées et sorties
Déterminer les entrées et les sorties du programme.
Trouver une méthode de calcul ou comprendre celle proposée
En se dotant d’un jeu d’exemples, il faut réussir à résoudre le problème en agissant comme un
automate, sans avoir à réfléchir.
Cas particuliers
Envisager les cas particuliers en essayant d’être le plus exhaustif possible.
Quelles instructions ?
En analysant la méthode de résolution, faire l’inventaire des instructions structurées qui seront
nécessaires.
Agencement des instructions
En prenant deux par deux les instructions trouvées à l’étape précédente, il faut déterminer leur
agencement (avant, après, dedans, autour) pour aboutir à un schéma de programme.
Formalisation de l’algorithme
Résumer et formaliser vos remarques sous forme d’algorithme.
Écriture du code source
Traduire le plus fidèlement possible l’algorithme dans le langage de programmation choisi.
Bien sûr, chaque fois que nous écrirons du code intermédiaire, nous vérifierons que le code conçu
réponde aux attentes en simulant l’exécution non seulement sur le jeu d’exemples défini à la deuxième
étape mais aussi sur les cas particuliers identifiés à la troisième. La dernière étape accomplie, on testera
le programme sur les mêmes exemples et cas particuliers.
— le code du while est vraiment à répéter plusieurs fois. Il s’agit alors d’une mauvaise
compréhension du fonctionnement de la boucle while et surtout de sa syntaxe. Dites-
vous bien qu’il n’y a pas de else à la suite d’un while ! Pour corriger ce problème, il
suffit bien souvent de supprimer le else tout en laissant les instructions qu’il contient.
— le corps du while n’est pas à répéter mais il ne faut l’exécuter que lorsque la condi-
tion est vérifiée. Dans ce cas, il s’agit plus d’une confusion entre le while et le if. Le
while permet de répéter un code tant qu’une condition est vérifiée. Il y a répétition des
instructions. Le if permet de réaliser des calculs différents en fonction de conditions. Il
n’y a pas de répétition du code. Pour résoudre ce problème, il suffit de remplacer le mot-clé
while par if.
utilisation du while à la place du if
ici et en l’absence de else, le compilateur ne détectera pas d’erreur. Ce n’est uniquement
lors de l’exécution de votre programme que vous vous apercevrez que ce dernier "boucle" à
l’infini : il n’affiche rien et ne vous redonne pas la main. C’est normal. Vous attendant à ce que
le corps du while ne soit exécuté qu’une seule fois, vous ne vous êtes pas soucié de changer
la valeur des variables utilisées dans la condition de la boucle et il n’y a pas lieu de le faire car
il ne faut pas boucler sur ces instructions. Il s’agit de nouveau d’une confusion entre les deux
instructions (while et if). La correction à apporter ne consiste qu’à remplacer le mot-clé
while par if.
utilisation du if à la place du while
comme pour le cas précédent, il n’y a pas d’erreur à la compilation. Ce n’est que lors de
l’exécution du programme que vous vous apercevez d’un mauvais fonctionnement de votre
programme : il ne fait qu’une seule fois les instructions et même, parfois, il ne les exécute
pas. Il s’agit là encore d’une confusion entre les deux instructions bien souvent associée à
l’utilisation de l’expression boucle if qui, rappelons-le, est absolument à bannir. Un simple
remplacement du mot-clé if par while suffit à résoudre ce problème.
Tableaux
Jusqu’ici, nous avons employé les variables pour stocker les valeurs individuelles de types primi-
tifs : une variable de type int pour stocker un entier, une variable de type boolean pour un booléen,
etc.
Un tableau est une structure regroupant plusieurs valeurs de même type, chacune étant stockée
dans un espace particulier appelé une case du tableau. On peut traiter un tableau comme un tout, ou
case par case. Traité comme un tout, on pourra le stocker dans une variable, le passer en paramètre
ou le donner en résultat d’un calcul. Chaque case est désignée individuellement via son indice, qui
correspond à sa position dans le tableau, et peut être traitée comme variable individuelle : on pourra
consulter sa valeur, la modifier, etc.
0 1 2 3 4 5 6 } Indices
tab = • } Cases
↓
tab[2] = case d’indice 2
Les tableaux sont des structures des données présentes dans tous les langages de programmation.
1
5.2. DÉCLARATION ET CRÉATION CHAPITRE 5. TABLEAUX
Un type tableau, comme int[] par exemple, peut servir à déclarer une variable.
int[] var;
Il n’y a rien de nouveau ici, c’est une déclaration de variable classique avec le type de la variable,
puis son nom.
Cette déclaration ne crée pas de tableau. Elle crée un nom que l’on pourra utiliser pour désigner
un tableau. Ce nom est associé à un espace dans la mémoire qui pourra contenir un tableau.
Un tableau a une taille fixée lors de sa création, c’est le nombre de cases qu’il contient. Un fois
créé, le tableau ne peut plus jamais changer de taille : on ne peut ni lui ajouter ni lui enlever de cases.
Pour créer un tableau, on utilise new suivi du type des éléments et du nombre de cases entre
crochets.
var = new int[10];
L’expression new int[10] crée un nouveau tableau de 10 cases, chaque case pouvant contenir
un nombre entier, et elle renvoie ce tableau. Le reste de la ligne est une affectation normale qui permet
de donner une valeur à la variable var.
A partir de ce moment, il est possible d’utiliser chaque case du tableau pour y stocker une valeur.
Chaque case se comporte comme une variable de type int.
tab[0]=10;
tab[1]=tab[0]+1;
3. L’initialisation des cases avec des valeurs par défaut, est réalisée implicitement par l’opération
de création.
Étudions plus en détail ces étapes.
Déclaration
L’instruction :
T [] tab;
déclare une variable tab destinée à contenir un tableau, dont les cases seront de type T. Après décla-
ration, la variable tab existe, mais n’est pas encore initialisée à un tableau.
Exemples
:
Après ces déclarations, les variables tabNum, t et m existent, mais pas encore la suite de cases
que chacune d’entre elles pourra désigner. Par exemple, il est impossible de modifier la première case
de tabNum (notée tabNum[0]) : elle n’existe pas encore. Le compilateur signale l’erreur :
Test.Java:7: variable tabNum might not have been initialized
tabNum[0] = 5;
^
Création
L’opération de création :
new T[n];
Après l’instruction de création new, la variable tab est initialisée à un tableau contenant trois
entiers. Après l’affectation tab[0] = 7, la structure du tableau en mémoire est :
tab −→ 7 0 0
Il est possible de réunir la déclaration et la création d’un tableau en une seule instruction. On
pourra ainsi déclarer et créer le tableau de l’exemple 1 par :
Il est alors inutile de réaliser une création explicite via new : elle se fait automatiquement à la
taille nécessaire pour stocker le nombre des valeurs données. En mémoire on aura :
tab −→ 1 9 2 4
tb −→ 0 0 0
Exemple 5 : Modification d’une case, accès en dehors des bornes d’un tableau.
Dans cet exemple, le calcul consiste à utiliser le contenu d’une variable, mais on peut tout aussi
avoir un calcul avec opérateur ou avec un appel de méthode qui renvoie un nombre entier.
int nb = 2;
char[] tab = new char[15];
tab[nb*2] = 57;
tab[Math.min(nb,17)] = 8;
tab[Math.min(nb,17)*2-1] = 5;
Ce programma affiche :
Java/Essais> Java AfficheTab
tab[0] = 10
tab[1] = 20
tab[2] = 30
tab[3] = 40
Une erreur commune dans une boucle, est de fixer le dernier indice à i <= tab.length plu-
tôt qu’à i <= tab.length -1. Si nous changeons la condition de la boucle de cette manière,
l’exécution produit une erreur : l’indice tab.length, égal à 4 ici, n’existe pas dans tab.
Java/Essais> Java AfficheTabErr
tab[0] = 10
tab[1] = 20
tab[2] = 30
tab[3] = 40
Exception in thread "main" Java.lang.ArrayIndexOutOfBoundsException: 4
at AfficheTabErr.main(AfficheTabErr.Java:5)
5 n = Terminal.lireInt();
6 int [] tab = new int[n];
7 // Initialisation
8 for (int i=0; i<= tab.length -1; i++) {
9 Terminal.ecrireString("Un entier? ");
10 tab[i] = Terminal.lireInt();
11 }
12 // Recherche de min et max
13 int min = tab[0];
14 int max = tab[0];
15 for (int i=1; i<= tab.length -1; i++) {
16 if (tab[i] < min) {
17 min = tab[i];
18 }
19 if (tab[i] > max) {
20 max = tab[i];
21 }
22 }
23 Terminal.ecrireStringln("Le minimum est: " + min);
24 Terminal.ecrireStringln("Le maximum est: " + max);
25 }
26 }
Notons que dans ce programme, l’algorithme proprement dit occupe les lignes 12 à 18. Il est
précédé par une suite d’instructions servant à créer et initisaliser le tableau et suivi d’un affichage
des résultats. Ce découpage en trois phases – lecture, calculs, affichage – est très courant et souvent
recommandé car il permet de séparer le cœur de l’algorithme des opérations d’interface avec l’utilisa-
teur.
Voici une exécution du programme :
Java/Essais> Java minMax
Combien des nombres a saisir? 5
Un entier? 5
Un entier? 2
Un entier? -6
Un entier? 45
Un entier? 3
Le minimum est: -6
Le maximum est: 45
int nombreNotes;
Terminal.ecrireString("Nombre de notes a saisir? ");
nombreNotes = Terminal.lireInt();
double [] lesNotes = new double[nombreNotes];
// Initialisation
for (int i=0; i<= lesNotes.length -1; i++) {
Terminal.ecrireString("Note no. " + (i+1) + "? ");
lesNotes[i] = Terminal.lireDouble();
}
double min = lesNotes[0];
double max = lesNotes[0];
double somme = 0;
int sup10 = 0;
for (int i=0; i<= lesNotes.length -1; i++) {
if (lesNotes[i] < min) { min = lesNotes[i];}
if (lesNotes[i] > max) { max = lesNotes[i];}
if (lesNotes[i] >= 10) { sup10++;}
somme = somme + lesNotes[i];
}
Terminal.ecrireString("La moyenne des notes est: ");
Terminal.ecrireDoubleln(somme/nombreNotes);
Terminal.ecrireStringln("Le nombre de notes >= 10 est: " + sup10);
Terminal.ecrireStringln("La note minimum est: " + min);
Terminal.ecrireStringln("La note maximum est: " + max);
}}
int n;
char [] t;
Terminal.ecrireString("Combien de caracteres a saisir? ");
n = Terminal.lireInt();
t = new char[n];
// Initialisation
for (int i=0; i<= t.length-1; i++) {
Terminal.ecrireString("Un caractere: ");
t[i] = Terminal.lireChar();
}
// Affichage avant inversion
Terminal.ecrireString("Le tableau saisi: ");
for (int i=0; i<= t.length-1; i++) {
Terminal.ecrireChar(t[i]);
}
Terminal.sautDeLigne();
// Inversion: arret si (i >= j)
int i,j;
char tampon;
for (i=0, j= t.length-1 ; i < j; i++, j--) {
tampon = t[i];
t[i] = t[j];
t[j] = tampon;
}
// Affichage final
Terminal.ecrireString("Le tableau inverse: ");
for (int k=0; k<= t.length-1; k++) {
Terminal.ecrireChar(t[k]);
}
Terminal.sautDeLigne();
}
}
Pour échanger les valeurs de deux cases du tableau, on est obligé d’utiliser une variable pour
stocker temporairement la valeur d’une des deux cases. Si l’on écrivait le code suivant :
t[i]=t[j];
t[j]=t[i];
on aurait juste recopié la valeur de t[j] dans t[i]. Quant à la valeur quavait initialement t[i],
elle est perdue après la première affectation. C’est donc la nouvelle valeur donnée à t[i] qui serait
copiée dans t[j] par la deuxième affectation. Celle-ci ne sert donc à rien. Le code correct consiste à
sauvegarder la valeur initiale de t[i] dans tampon :
tampon = t[i];
t[i] = t[j];
t[j] = tampon;
Un exemple d’exécution du programme Inversion :
9 if (tab[numcase]==elem){
10 appartient = true;
11 }
12 }
13 if (appartient){
14 System.out.println(elem + " appartient au tableau");
15 }else{
16 System.out.println(elem + " n’appartient pas au tableau");
17 }
18 }
19 }
Ce serait une grave erreur de changer la boucle avec un else dans le cas où tab[numcase] est
différent de elem. Le fait que l’élément ne soit pas celui qu’on cherche ne permet pas du tout de dire
que l’élément n’appartient pas au tableau.
Lequel des deux programmes RechercheFor et RechercheWhile est meilleur ? Si le tableau est
très grand, il peut y avoir un petit avantage à utiliser RechercheWhile en terme de temps d’exécution.
Dans la plupart des cas, la différence ne sera pas perceptible et on peut employer indifféremment l’un
ou l’autre. RechercheFor est plus simple à lire et RechercheWhile théoriquement plus efficace.
6.1.1 Exemple
1
6.1. VARIABLES ET MÉMOIRE CHAPITRE 6. EXÉCUTION ET MÉMOIRE
L’on peut représenter le contenu de la mémoire après l’exécution de cette déclaration de la façon
suivante :
Mémoire
euros : ?
L’exécution de la ligne 4 du programme est de même nature : il s’agit de l’allocation d’un espace
mémoire de 8 octets à la variable dollars. L’état de la mémoire devient le suivant.
Mémoire
euros : ?
dollars : ?
Mémoire
euros : 10.0
dollars : ?
La ligne 7 est une affectation à la variable dollars. Le calcul utilise la valeur de la variable
euros qui va être lue dans la mémoire privée de la variable. Après exécution de cette ligne 7, la
mémoire est dans l’état suivant.
Mémoire
euros : 10.0
dollars : 11.18
L’état de la mémoire ne change plus par la suite, car il n’y a ni déclaration ni affectation dans les
lignes 8 et 9 du programme.
La variable comment commence à exister à la ligne où elle est déclarée et elle cesse d’exister à la
fin de la suite d’instruction du cas else, c’est à dire à la ligne 11. Lors d’une exécution, si la condition
du if est vraie, c’est le cas if qui est exécutée, la variable n’existe à aucun moment. Si la condition est
fausse, elle commence à exister après l’exécution de la ligne 7 et elle cesse d’exister après l’exécution
de la ligne 10. En particulier, lors de l’exécution de la ligne 12, la variable comment n’existe pas dans
la mémoire.
Dans le cas d’une boucle while, si une variable est déclarée dans le corps de la boucle, un nouvelle
variable est créée à chaque tour de boucle, au moment où cette déclaration est exécutée et la variable
cesse d’exister à la fin de ce tour de boucle, à la fin de la suite d’instruction qui constitue le corps de
la boucle.
Dans tous les cas, la variable existe jusqu’à la fin de la suite d’instructions où elle a été déclarée,
cette fin étant matérialisée dans le programme par une accolade fermante : celle qui ferme la méthode,
celle qui ferme le cas if ou ou le cas else, celle qui ferme le corps d’une boucle.
Il y a un cas un peu différent, celui d’une variable déclarée dans la partie initialisation d’une boucle
for. Une variable de ce type n’existe que pendant l’exécution de la boucle, mais elle est la même pour
tous les tours de boucle, ce qui est un comportement différent d’une variable déclarée dans le corps de
la boucle. Il n’est pas possible d’utiliser cette variable après le for : elle n’existe plus ; si on a besoin
de sa valeur après, il faut la déclarer avant la boucle.
Voici un cas incorrect :
A la compilation : erreur !
A la ligne 6, la variable n’existe plus. Elle a cessé d’exister à la fin de l’exécution du for.
Voici comment corriger ce programme pour qu’il soit correct :
La variable déclarée dans la suite d’instructions de la méthode main existe jusqu’à la fin de cette
suite d’instructions, jusqu’à la fin de l’exécution du programme.
l’état initial, avant exécution de la première instruction. Un point d’interrogation est utilisé pour une
variable déclarée mais à laquelle aucune valeur n’a encore été affectée.
La ligne d’en-tête d’une boucle for contient trois parties exécutées à des moments différents du
programme : l’initialisation, le test et l’incrémentation. Lorsqu’on mentionne cette ligne dans le ta-
bleau relatant l’exécution, il faut préciser laquelle des trois parties est exécutée.
1 class Exemple5_0{
2 public static void main(String[] args){
3 int total = 0;
4 int x;
5 Terminal.ecrireString("Entrez le multiplicateur: ");
6 x = Terminal.lireInt();
7 for (int i=1; i<=4; i++){
8 total = total + (i*x);
9 }
10 Terminal.ecrireString("La somme des 4 premiers multiples est: ");
11 Terminal.ecrireInt(total);
12 Terminal.sautDeLigne();
13 }
14 }
— le Tas (anglais : Heap) qui contient les mémoires privées des tableaux.
Les emplacements dans la pile sont repérés par des noms et dans le tas par des adresses. Que
sont les adresses ? Des identifiants uniques qui permettent de repérer un emplacement précis de la
mémoire. On verra bientôt qu’en Java, on peut (plus ou moins) afficher les adresses et faire des tests
d’égalité ou de différence des adresses.
Tout ce que nous avons dit à propos des variable dans la section précédente est vrai pour toutes
les variables, y compris celles des types tableaux. Mais l’instuction new a un comportement tout à fait
nouveau que nous allons présenter au moyen d’un exemple simple.
La ligne 3 a pour effet de réserver une mémoire privée pour la variable tab dans la pile. L’état de
la mémoire après exécution de cette ligne est le suivant :
Pile Tas
tab : ?
Pile Tas
La ligne suivante est une affectation à une case du tableau. Cette affectation change le contenu
d’une case du tableau dans la mémoire privée du tableau, dans le tas. L’état mémoire devient le suivant.
Pile Tas
La dernière ligne du programme consiste à afficher le contenu de la mémoire privée de tab, autre-
ment ce que nous avons appelé adresse-1.
Une exécution réelle affiche la chaîne suivante.
Cet affichage ne nous dit pas grand chose : c’est juste un nom donné par la machine virtuelle Java
à l’emplacement de la mémoire privée du tableau.
Pile Tas
La ligne 5 consiste à lire le contenu de la mémoire privée de la variable tab1 et à l’écrire dans la
mémoire privée de la variable tab2. Cela conduit à l’état suivant.
Pile Tas
Il y a deux variables différentes qui contiennent l’adresse du seul tableau qui existe dans le tas. Du
coup, il y a deux façons différentes d’accéder à une case du tableau, par exemple celle d’indice 0. Ces
deux façons s’écrivent respectivement tab1[0] et tab2[0]. Ce sont deux façon différentes de désigner
le même emplacement de la mémoire, dans le tas.
Les lignes 6 et 7 affichent les adresses contenues dans tab1 et tab2, ce qui permet de constater que
ce sont une seule et même adresse.
La ligne 8 modifie tab1[0], en y mettant true à la place de false. Ce faisant, le programme modifie
aussi tab2[0], puisque dans l’état où est le programme, tab1[0] et tab2[0] sont une seule et même
chose.
En fin de programme, l’état de la mémoire est le suivant.
Pile Tas
}else{
System.out.println("t1 et t2 ne sont pas égaux");
}
if (t1 != t2){
System.out.println("t1 et t2 sont différents");
}else{
System.out.println("t1 et t2 ne sont pas différents");
}
}
}
Notons que la ligne int[] t1 = {10, 20}; crée un nouveau tableau, comme s’il y avait un
verb/new int[2]/ suivi de deux affectations pour mettre 10 et 20 dans les deux cases du tableau. Dans
cet exemple, il y a donc deux variables et deux tableaux à deux adresses différentes.
Voyons ce que donne l’exécution de ce programme.
Les deux opérateurs == et != ne comparent pas les deux tableaux contenus dans le tas, mais juste
les deux adresses stockées dans la pile, dans les deux espaces privés des variables t1 et t2.
Pour bien illustrer le cas, voyons l’état de la mémoire après déclaration des deux variables.
Pile Tas
t1 : adresse-1
int[]
t2 : adresse-2 adresse-1 0 10
1 20
int[]
adresse-2 0 10
1 20
Voyons maintenant un cas où deux tableaux sont considérés comme égaux par ces opérateurs. Il
suffit de reprendre et d’augmenter le programme Memoire2 de la section précédente.
public class Memoire2Bis{
public static void main(String[] args){
boolean[] tab1, tab2;
tab1 = new boolean[3];
tab2 = tab1;
System.out.println(tab1);
System.out.println(tab2);
tab1[0] = true;
System.out.println(tab2[0]);
if (tab1 == tab2){
3 double[] tab;
4 tab=new double[3];
5 tab[0]=0.5;
6 tab=new double[3];
7 tab[1]=1.6;
8 }
9 }
Pile Tas
Ligne 6, il y a création d’un nouveau tableau par l’instruction new à une nouvelle adresse,
adresse-2. Cette adresse est affectée à la variable tab. Après ces opérations, l’état de la mémoire
est le suivant.
Pile Tas
Dans cet état, le programme ne peut plus accéder au tableau dont la mémoire privée est à l’adresse
1 car il n’y a plus de variable qui contienne son adresse. Il n’existe pas en Java d’instruction qui
permettrait de retrouver ce tableau à partir de cet état. Dès lors, que ce tableau soit présent en mémoire
ou pas, cela ne change pas le résultat de l’exécution et c’est pourquoi le système peut récupérer cet
emplacement s’il est à court de mémoire.
String
msg1 : adresse-1 adresse-1 Bonjour
msg2 : adresse-2
String
adresse-2 Au revoir
La ligne 5 déclare une nouvelle variable msg3 et lui affecte le contenu de msg1, c’est-à-dire
adresse-1.
L’état de la mémoire en fin de programme est donc le suivant.
Pile Tas
String
msg1 : adresse-1 adresse-1 Bonjour
msg2 : adresse-2
msg3 : adresse-1 String
adresse-2 Au revoir
Dans cet état msg1 et msg3 sont deux moyens d’accéder à la même chaîne de caractères, à la
même mémoire privée.
Comme pour les tableaux, == et != testent si deux expressions Java désignent la même mémoire
privée et non pas si les deux expressions désignent la même suite de caractères. En effet, il est possible
d’avoir en mémoire deux mémoires privées contenant la même suite de caractères. Dans ce cas, == et
!= considèrent ces deux chaînes comme différentes. La programme suivant illustre cette situation.
1 public class Memoire5{
2 public static void main(String[] args){
3 int x = 2;
4 String msg1 = "Bonjour2";
5 String msg2 = "Bonjour"+x ;
6 System.out.println("<"+msg1+">");
7 System.out.println("<"+msg2+">");
8 if (msg1==msg2){
9 System.out.println("chaîne égales selon ==");
10 }else{
11 System.out.println("chaîne différentes selon ==");
12 }
13 }
14 }
Pile Tas
String
x : 2 adresse-1 Bonjour2
msg1 : adresse-1
msg2 : adresse-2 String
adresse-2 Bonjour2
Ceci explique pourquoi il ne faut pas utiliser == et != pour comparer deux chaînes de caractères
si l’on veut comparer leurs contenus et non pas l’adresse de leurs mémoires privées.
Pile Tas
String
msg1 : adresse-1 adresse-1 Bonjour
msg2 : adresse-2
msg3 : adresse-1 String
adresse-2 Au revoir
En remplaçant les adresses par des flèches, cela nous donne le schéma suivant.
Pile Tas
String
msg1 : • Bonjour
msg2 : •
msg3 : • String
Au revoir
Méthodes
Une méthode que l’on peut appeler également un sous-programme, une fonction ou une procédure,
est un petit programme qui réalise une tâche déterminée. Elle prend en entrée des paramètres et peut
renvoyer un résultat ou ne pas renvoyer de résultat.
1
7.1. RAPPEL DE CE QUE L’ON A DÉJÀ VU CHAPITRE 7. MÉTHODES
String
System.out.println Terminal.lireInt
int
int int
Math.random
Math.min
double
int
Les méthodes qui renvoient un résultat ne sont généralement pas seules sur une ligne : elles ap-
paraissent au milieu d’un code. Par exemple, un appel de méthode peut apparaitre à droite d’une af-
fectation ou en tant que paramètre, entre les parenthèses d’un autre appel de méthode. Ces méthodes
sont parfois appelées des fonctions.
x = Math.min(x,12);
System.out.println(Math.min(x,12));
Lorsqu’on appelle une méthode, il faut lui fournir entre parenthèses le bon nombre de paramètres,
avec les bons types et dans le bon ordre. Ce qui apparaît entre les parenthèses sont des expressions.
Au moment de l’exécution, chacune de ces expression est calculée et la valeur qui est le résultat du
calcul est transmis à la méthode appelée.
Il y a quatre sortes d’expressions : valeurs littérales, variables, expression avec opérateur, appel
d’une méthode qui renvoie un résultat. Chacune de ces sortes peut être utilisée dans un appel de
méthode.
int x = 12;
System.out.println(45); // valeur littérale
System.out.println(x); // variable
System.out.println(1+2); // expression avec opérateur
System.out.println(Math.min(x,5)); // appel de méthode
On peut se demander à quoi sert le paramètre de la méthode main. Il sert à transmettre des don-
nées entrées lors de la demande d’exécution du programme. Par exemple, si l’on demande l’exécution
d’un programme depuis une ligne de commande dans un terminal, on commence cette ligne par la
commande java, ensuite on met le nom de la classe contenant la méthode main et ensuite on peut
ajouter des données séparées par des espaces. Ces données sont transmises au programme dans le
tableau args.
Voyons quelques exemples d’appels d’un programme qui utilise ce paramètre.
public class ArgMain{
public static void main(String[] args){
System.out.println("Taille de args: " + args.length);
for (int i=0; i<args.length; i=i+1){
System.out.println("Contenu de args[" + i + "]: " + args[i]);
}
}
}
Supposons par exemple que nous voulions faire une jolie présentation, entrecoupée de lignes de
60 astérisques. Nous pourrions l’écrire :
public class Lignes1 {
public static void main(String[] args) {
dessinerLigne();
System.out.println("Un texte bien encadré");
dessinerLigne();
System.out.println("Après la ligne");
System.out.println();
System.out.println();
dessinerLigne();
}
Il y a deux méthodes dans la classe : main et dessinerLigne. L’ordre dans lequel sont écrites
les méthodes dans la classe n’a aucune importance. Ici, il y d’abord le main puis dessinerLigne.
Ce pourrait être l’inverse sans changer le fonctionnement du programme.
La méthode dessinerLigne est définie dans la classe Lignes1. Par convention, en Java, le
nom d’une méthode est supposé commencer par une minuscule.
La méthode est appelée plusieurs fois dans le main. L’appel d’une méthode sans résultat a la
forme :
NomDeLaClasse.nomDeLaMéthode() ;
Si l’appel de la méthode est dans la même classe que la méthode appelée, on peut ne pas mettre le
nom de la classe qui est alors sous-entendu et l’appel devient :
nomDeLaMéthode() ;
Lors de l’exécution de l’appel, les instructions de la méthode sont exécutés, puis on revient dans
le main, et les instructions venant après l’appel sont exécutées à leur tour.
Une méthode peut en appeler d’autres. On pourrait par exemple créer la méthode
dessinerDeuxLignes() :
public class Ligne1 {
... même code que précédemment...
Une variable locale n’existe que le temps de l’exécution de la méthode qui la déclare. Si deux
méthodes déclarent des variables de même noms, ces variables sont complètement distinctes.
Ainsi, dans le programme suivant :
public class VarLocales {
Nous avons deux variables nommées a : une qui est déclarée dans le main, l’autre qui est dé-
clarée dans dessinerLigne. Quand le main appelle la méthode dessinerLigne, une seconde
variable, nommée a est définie. Elle est complètement différente de la première, et occupe un autre
espace mémoire.
Le début de l’exécution du programme donne donc la trace suivante :
ligne a (de main) a (de dessinerLigne)
13 20 (n’existe pas)
14 20 (n’existe pas)
4 20 0
5 20 0
6 20 0
7 20 1
... exécution de la boucle ...
9 20 60
15 20 (n’existe pas)
La variable a définie dans le main existe jusqu’à la fin de l’exécution de celui-ci. La variable a de
dessinerLigne est créée au début de l’exécution de dessinerLigne, et disparaît à la fin de
l’exécution de cette méthode.
On remarquera que
— dans dessinerLigne, on ne voit pas les variables définies dans le main. Celles-ci oc-
cupent une place en mémoire, mais on ne peut pas y accéder (ni a fortiori les modifier) depuis
dessinerLigne.
— si on appelait plusieurs fois dessinerLigne à partir du main, à chaque appel correspondrait
une nouvelle variable a, initialisée à 0. C’est normal, puisque les variables locales des appels
précédents auront été détruites.
dessinerLigne(30);
Une méthode peut donc avoir une liste d’paramètres (éventuellement vide). Cette liste, ainsi que
le nom de la méthode, constitue ce que l’on appelle la signature de la méthode.
On déclare les paramètres entre parenthèses, juste après le nom de la méthode. On déclare
un paramètre comme une variable, en le faisant précéder de son type. Ainsi, la nouvelle méthode
dessinerLigne pourrait s’écrire :
public class ProcedureAvecParamètres {
public static void dessinerLigne(int longueurLigne) {
for (int i= 0; i < longueurLigne; i++) {
Terminal.ecrireChar(’*’);
}
Terminal.sautDeLigne();
}
La méthode pourra être appelée depuis une autre méthode de la même classe (par exemple depuis
main). Pour appeler une méthode qui prend des paramètres, on écrit le nom de la méthode, suivi des
valeurs des paramètres, entre parenthèses.
....
public static void main(String[] args) {
int k= 50;
dessinerLigne(k);
dessinerLigne(k/2);
dessinerLigne(k/4);
}
}
Une méthode peut aussi avoir plusieurs paramètres. Notre méthode dessinerLigne actuelle
utilise forcément le caractère ’*’. Il serait intéressant de pouvoir dessiner des lignes de ’-’ ou de ’+’,
par exemple. C’est possible en créant une méthode avec plusieurs paramètres :
public class ProcedureAvecParamètres2 {
public static void dessinerLigne(int longueurLigne, char symbole) {
for (int i= 0; i < longueurLigne; i++) {
Terminal.ecrireChar(symbole);
}
Terminal.sautDeLigne();
}
Dans cet exemple, nous définissons une méthode dessinerLigne qui prend deux paramètres :
le premier est un entier, longueurLigne, et le second est un caractère, symbole. Les paramètres
sont ensuite utilisés dans le corps de la méthode.
Plus précisément, la liste des paramètres d’une méthode est composée d’une suite de déclarations
de variables, séparées par des virgules. Attention, on doit répéter le type devant chaque paramètre. Si
j’ai par exemple deux paramètres entiers, je devrais écrire :
public static void dessinerRectangle(int largeur, int hauteur) {
....
}
Quand on appelle une méthode en Java, il est obligatoire de fournir des valeurs pour tous ses
paramètres. L’ordre des paramètres est important, ainsi que leur type. Pour appeler la méthode
dessinerRectangle de l’exemple précédent, il faut passer deux entiers. Le premier donnera
sa valeur au paramètre largeur, le second au paramètre hauteur.
De même, pour appeler la dernière version de notre méthode dessinerLigne, on devra passer
d’abord un entier (la longueur de la ligne), puis un caractère (le symbole à utiliser). Donc, pour
dessiner une ligne de 50 ’-’, j’écrirai :
dessinerLigne(50, ’-’);
double x= 3;
double y= Math.cos(x/2);
La méthode valeurAbsolue déclare dans son en-tête qu’elle retourne une valeur de type
double. La valeur est effectivement renvoyée ligne 8, à l’aide de l’instruction return.
La forme de cette instruction est
return EXPRESSION ;
où EXPRESSION est du même type que la méthode. L’exécution du return a deux effets :
1. elle fixe la valeur de retour ;
2. elle termine l’exécution de la méthode.
On aurait pu aussi écrire :
public static double valeurAbsolue(double x) {
if (x < 0)
return -x;
else
return x;
}
mais on considère généralement qu’il est plus élégant de ne placer qu’un seul return dans la
méthode (on sait alors exactement où la méthode se termine, et cela simplifie sa relecture).
Attention, une méthode doit toujours se terminer par un return 1 Si Java détecte (parfois à tort)
qu’il est possible que ça ne soit pas le cas, il refuse de compiler. La méthode suivante, par exemple :
public static double valeurAbsolue(double x) {
if (x < 0){
return -x;
}
}
7.6 Varia
7.6.1 Notion d’effet de bord
La tâche principale d’une méthode est de renvoyer une valeur. Il arrive cependant qu’une méthode
modifie aussi son environnement (l’affichage, le contenu d’un fichier, etc...).
C’est un procédé à utiliser avec précaution, dans la mesure où il est souhaitable qu’une méthode
ait une tâche unique et bien définie.
Exceptions
8.1 Introduction
Les exceptions sont un mécanisme qui permet de gérer certaines erreurs, notamment celles qui
proviennent des méthodes prédéfinies et qui conduisent à un arrêt brutal du programme. Par exemple,
si l’on cherche à accéder à une case de tableau qui n’existe pas, l’exception ArrayIndexOutOfBound-
sException est déclenchée et le programme s’arrête. Autre cas : le programme lit un entier avec Ter-
minal.lireInt() et l’utilisateur tape des lettres ou des signes de ponctuation : c’est cette fois l’erreur
TerminalException qui est activée.
De façon plus générale, les exceptions sont utilisées quand une erreur est détectée par une méthode
(prédéfinie ou pas) et que le traitement de cette erreur n’est pas réalisée à l’endroit où elle est detectée
mais à celui où la méthode a été appelée.
Le mécanisme d’exception introduit une perturbation dans l’ordre d’exécution des instructions
d’un programme.
Il y a trois temps dans la gestion des exceptions :
— la création de l’exception
— le déclenchement de l’exception à l’endroit où une erreur est détectée
— la désactivation de l’exception à l’endroit où le problème est résolu
La terminologie employée dans le langage Java est la suivante :
— pour le déclenchement de l’exception : on dit que l’exception est lancée (to throw en anglais).
Dans d’autres langages, c’est le terme lever (to raise) qui est employé.
— pour la désactivation de l’exception : on dit que l’exception est attrapée (to catch en anglais)
Le programmeur ne programme pas toujours les trois temps. Dans le cas des méthodes prédéfinies,
la création de l’exception et son déclenchement sont prédéfinis dans la librairie Java et le programmeur
n’a plus qu’à résoudre le problème. Nous allons étudier d’abord cette situation avant de voir comment
le programmeur peut créer et déclencher ses propres exceptions.
1
8.2. TRAITER LES EXCEPTIONS PRÉDÉFINIES CHAPITRE 8. EXCEPTIONS
3 int[] tab;
4 int taille, indice;
5 Terminal.ecrireString("Taille du tableau? ");
6 taille = Terminal.lireInt();
7 tab = new int[taille];
8 for (int i=0; i<taille; i=i+1){
9 Terminal.ecrireString("Entrez un nombre: ");
10 tab[i]=Terminal.lireInt();
11 }
12 Terminal.ecrireString("Quelle case voulez-vous doubler? ");
13 indice = Terminal.lireInt();
14 tab[indice]=tab[indice]*2;
15 for (int i=0; i<taille; i=i+1){
16 Terminal.ecrireString(tab[i]+" ");
17 }
18 Terminal.sautDeLigne();
19 }
20 }
Entrez un nombre: 10
Entrez un nombre: 5
Entrez un nombre: 7
Quelle case voulez-vous doubler? 10
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 10
at Excep_1.main(Excep_1.java:14)
try{
instruction-1
instruction-2
...
instruction-n
}catch(NomDException e){
autre-instruction-1
autre-instruction-2
...
autre-instruction-n
}
try{
liste-instuctions-1
}catch(Exception1 e){
liste-instructions-2
}catch(Exception2 e){
liste-instructions-3
}
Les deux exemples suivants montrent que plus ou moins d’instructions sont exécutées selon le
moment où la saisie est incorrecte.
Mais dans les deux cas d’exception, les dernières instructions de la voie normale (lignes 13 à 19)
ne sont pas exécutées.
On a été obligé de faire plusieurs try...catch différents pour éviter d’avoir des instructions non
exécutées dans le cas où la saisie est erronée.
Un exemple d’exécution :
> java Excep_1_2
Taille du tableau? a
On vous demandait un nombre entier, Veuillez rentrer un nombre entier: 3
Entrez un nombre: 5
Entrez un nombre: 1
Entrez un nombre: a
On vous demandait un nombre entier, Veuillez rentrer un nombre entier: 4
Quelle case voulez-vous doubler? a
On vous demandait un nombre entier, Veuillez rentrer un nombre entier: 1
5 2 4
Le comportement du programme est meilleur, mais il n’est pas parfait, car s’il y a une erreur sur
la deuxième chance de saisie, le programme plante.
Terminal.ecrireIntln(entier);
}
}
Exécution :
Dans notre exemple, nous avons trois instructions Terminal.lireInt. Plutôt que de répéter la boucle
trois fois, nous allons créer une méthode pour réaliser la lecture des entiers.
public class Excep_1_3{
public static int lireEntier(String msg){
int entier=0;
boolean correct;
do{
try{
Terminal.ecrireString(msg);
entier = Terminal.lireInt();
correct=true;
}catch(TerminalException e){
Terminal.ecrireString("Vous avez tapé autre chose qu’un entier");
Terminal.ecrireStringln(" Veuillez recommencer");
correct = false;
}
}while(!correct);
return entier;
}
public static void main(String[] args){
int[] tab;
int taille, indice;
taille = lireEntier("Taille du tableau? ");
tab = new int[taille];
for (int i=0; i<taille; i=i+1){
tab[i]=lireEntier("Entrez un nombre: ");
}
indice=lireEntier("Quelle case voulez-vous doubler? ");
tab[indice]=tab[indice]*2;
for (int i=0; i<taille; i=i+1){
Terminal.ecrireString(tab[i]+" ");
}
Terminal.sautDeLigne();
}
}
Exécution :
Ce programme est devenu assez compliqué, mais grâce à cela, il n’est plus à la merci d’une frappe
incohérente de l’utilisateur. Même si un petit chat rentre n’importe quoi en machant sur le clavier, le
programme ne plantera pas !
Le problème de ce codage, c’est que si l’on donne un paramètre négatif à factorielle, le résultat
obtenu est erroné. La fonction factorielle n’est définie que pour les nombres positifs ou nuls.
On peut créer une exception spécifique pour gérer cette erreur et empêcher le programme de
donner un résultat faux. Appelons-la FactorielleNonDefinie. Pour créer cette exception il faut écrire
le code suivant :
class FactorielleNonDefinie extends RuntimeException {}
Ce code peut être mis au début du fichier contenant le programme, avant la déclaration de la classe
qui contient la méthode main.
Il y a là deux morceaux : throw est une instruction qui lance l’exception et new est qui crée
l’exception lancée. Ce new est le même que celui utilisé pour créer un tableau. Il y a une création de
quelque chose de nouveau stocké dans le tas.
Exemple d’exécutions :
}
}while(!correct);
}
}
Exemple d’exécution :
Cette méthode peut se réécrire plus simplement avec le même comportement de la façon suivante.
public class Excep_2{
public static int lireEntierIntervalle(int min, int max){
int res=0;
boolean correct;
do{
Retenez bien qu’un throw ne doit jamais être écrit directement dans le try...catch qui attrape l’ex-
ception : il doit être écrit dans une méthode appelée directement ou indirectement dans le try...catch.
int x;
System.out.println("Debut du main");
System.out.print("Entier: ");
x = Terminal.lireInt();
try{
methode1(x);
System.out.println("Fin du try");
}catch(LException e){
System.out.println("Exception capturee");
}
System.out.println("fin du main");
}
}
Exécution :
Dans ce programme, on a choisit de mettre le try dans la méthode main. On aurait pu le mettre
aussi dans methode1 ou methode2.
0 1 0 1 0 1
12 7 8 9 3 2
Le type de ce tableau s’écrit : int[][]. Il n’y a rien là de nouveau ou mystérieux : c’est la règle
normale qui s’applique. On prend le type de ce qu’on met dans chaque case du tableau (ici int[]) et
on ajoute des crochets.
int[][] tab;
tab = new int[3][2];
L’instruction new crée ici les 4 tableaux : le grand tableau à 3 cases et les 3 petits tableaux à deux
cases contenus dans les 3 cases en question. Dans chacune des cases de ces petits tableaux, il y a la
valeur par défaut du type int : 0.
Donc le tableau créé ici est le suivant :
1
9.1. CRÉER ET MANIPULER DES TABLEAUX
CHAPITRE
DE TABLEAUX
9. TABLEAUX À DEUX DIMENSIONS
0 1 2
0 1 0 1 0 1
0 0 0 0 0 0
Si l’on souhaite créer seulement le grand tableau, le code à écrire est le suivant :
int[][] tab;
tab = new int[3][];
Cela crée un tableau à 3 case avec dans chacune, la valeur par défaut du type int[], qui est
null.
0 1 2
null null null
On peut en avoir confirmation en affichant le contenu du tableau.
public class Tabtab{
public static void main(String[] args){
int[][] tab;
tab = new int[3][];
for (int i=0; i<tab.length; i=i+1){
System.out.println(tab[i]);
}
}
}
0 1 2
Il est possible de définir directement un tableau de tableau lors de la déclaration d’une variable,
avec les accolades.
int[][] tab = {{1,2},{3,4},{5,6}};
0 1 2
Il est possible également de réaliser une affectation sur l’ensemble d’un petit tableau contenu
dans une des cases. Par exemple, on peut affecter le contenu d’une case à une variable de type tableau
d’entiers.
int[][] tab;
int[] autreTab;
tab = new int[3][2];
autreTab = tab[2];
L’affectation dans l’autre sens d’un tableau d’entier à une case de tab est également possible.
Prenons comme exemple le tonnage de bateaux coulés et construits pendant la seconde guerre
mondiale retranscrit dans le tableau suivant.
Année 1939 1940 1941 1942 1943 1944 1945
Tonnage allié coulé 810 4 407 4 398 8 245 3 611 1 422 451
Tonnage construit 332 1 219 1 964 7 182 14 585 13 349 3 834
Si l’on ôte les têtes de colonnes et de lignes, les données qui sont des milliers de tonnes de navires,
occupent deux lignes et sept colonnes.
0 1 2 3 4 5 6
0 810 4 407 4 398 8 245 3 611 1 422 451
1 332 1 219 1 964 7 182 14 585 13 349 3 834
On peut les stocker dans un tableau de tableaux de deux façons : soit un grand tableau à deux cases
pour contenir chacune une ligne et dans chaque case un petit tableau à 7 cases pour les 7 années ; soit
un grand tableau à 7 cases avec dans chaque case un petit tableau à 2 cases. Selon ce qu’on veut faire
avec ces données, l’un ou l’autre choix peut être meilleur. Dans certains cas, le choix est arbitraire.
Voyons les deux possibilités écrites en Java.
Le programme affiche le nombre de milliers de tonnes construites (ligne 2) en 1942 (colonne 4).
On voit que l’accès à la case se fait avec les deux indices dans deux ordres différents.
Si l’on dessine les tableaux correspondants cela donne ce qui suit.
0 1 2 3 4 5 6
0 1 0 1 0 1 0 1 0 1 0 1 0 1
812 332 4407 1219 4398 1964 8245 7182 3611 14585 1422 13349 451 3834
0 1
0 1 2 3 4 5 6 0 1 2 3 4 5 6
812 4407 4398 8245 3611 1422 451 332 1219 1964 7182 14585 13349 3834
Dans la suite de la section, nous opterons pour la première version du tableau où les colonnes sont
données avant les lignes (cf. tabTonnage_v1).
En ce qui concerne les colonnes, cela n’aurait pas trop de sens d’ajouter des bateau coulés à
des bateaux construits. Ce qui a un sens dans cet exemple, c’est de faire la différence entre bateaux
construits et coulés pour avoir ce que l’on peut appeler une variation du tonnage disponible. Par
exemple, pour l’année 1942, ce calcul pour s’écrire comme suit :
int delta_tonnage_42 = tabTonnage_v1[3][1] -tabTonnage_v1[3][0];
System.out.println("variation du tonnage en 1942: "+
delta_tonnage_42);
Au lieu d’afficher les résultats, on peut les stocker dans un tableau à 7 cases.
int[] tab_delta = new int[tabTonnage_v1.length];
for (int col=0; col<tabTonnage_v1.length; col++){
tab_delta[col] = tabTonnage_v1[col][1] -tabTonnage_v1[col][0];
}
s’agit de structures qui n’ont pas de représentation graphique et sont donc assez abstraites. On parle
alors plutôt de vecteurs de dimension 4, 5, et au-delà. On utilise plus rarement ce genre de structures.
On définit un tableau de trois cases et dans chacune des trois cases il y a un tableau à deux cases.
Les cases du grand tableau ne contiennent pas directement les trois petits tableaux mais leurs adresses
ou références. De la même façon qu la variable tab dans la pile ne contient pas directement le grand
tableau, qui est dans le tas, mais son adresse.
Pile Tas
String[]
int[][]
0
1
1
int[]
0 1
1 2
int[]
0 5
1 6
Pile Tas
String[]
int[][]
0
1
1
int[]
0 1
Mémoire privée de main 1 2
args : •
tab : • int[]
0 3
1 4
2 5
int[]
0 6
Est-ce que ces tableaux assymétriques ont un intérêt en pratique ? Oui, parfois.
Par exemple, les distances entre villes sont souvent représentées par un tableau triangulaire : la
relation étant symétrique, il est inutile d’avoir une case pour la distance A-B et une autre pour la
distance B-A.
L’image suivante a été trouvée sur un site web touristique.
Autre exemple : si l’on veut retracer dans un tableau les notes d’un élève avec une colonne pour
chaque matière, rien ne dit que le nombre de contrôles est égal dans toutes les matières. Il peut donc
y avoir un nombre de lignes différent d’une colonne à l’autre.
a a a
X X X
c c c
d d d
On voit ici que l’affectation du caractère ’X’ à grand[0][1] a changé aussi grand[1][1]
et grand[2][1] qui sont trois noms différents qui désignent le même emplacement dans le tas.
L’état de la mémoire après l’affectation est dessiné ci-dessous.
Pile Tas
String[]
char[]
0 ’a’
1 ’X’
2 ’c’
3 ’d’
Mémoire privée de main
args : •
petit : •
char[][]
grand : •
0
1
2
9.4 Conclusion
Il n’y a rien de très nouveau dans ce chapitre. La seule réelle nouveauté est la possibilité de créer
d’un coup tous les tableaux d’une structure à plusieurs dimensions avec un new suivi de plusieurs
paires de crochets comme dans new int[3][5].
Pour le reste, la façon d’accéder aux cases, les affectations autorisées, la façon d’obtenir la taille
d’un tableau se font de la même façon que d’habitude.
Les tableaux à deux dimensions servent très souvent à représenter des données dans des applica-
tions de gestion. Ils permettent d’implémenter les matrices utilisées dans des applications de simula-
tion numérique.
Las tableaux à deux dimensions sont également très utiles pour représenter les jeux de damier
(échecs, dames, reversi), les jeux sur feuille (morpion, bataille navale, sudoku, mots croisés).
Nous avons vu des principes d’organisation de la mémoire utilisée pour l’exécution d’un pro-
gramme en deux espaces : la pile et le tas, les variables étant stockées dans la pile, les tableaux et les
chaînes de caractères étant stockées dans le tas.
Nous avons vu que les variables de type String ou de type tableau contiennent non pas directement
la chaîne ou le tableau, mais l’adresse d’un emplacement dans le tas où la chaîne ou le tableau est
stockée.
Il nous reste à compléter le modèle en détaillant ce qui se passe lorsqu’une méthode est appelée :
où sont stockés les paramètres et comment certaines valeurs sont utilisées par plusieurs méthodes.
1
10.1. MÉTHODES ET MÉMOIRE CHAPITRE 10. EXÉCUTION ET MÉMOIRE 2
7 res = nb2;
8 }
9 return res;
10 }
11 public static void main(String[] args){
12 int x;
13 x = 127;
14 x = min(x,12);
15 x = x *x;
16 System.out.println(x);
17 }
18 }
Le programme commence par l’exécution de la méthode main. Pour réaliser cette exécution, une
mémoire privée est allouée pour la méthode main. Cette mémoire contient en début d’exécution le
paramètre de la méthode qui s’appelle args et qui contient l’adresse d’un tableau de String en mé-
moire. Nous supposons ici que c’est un tableau vide, sans case, qui pourrait être créé par l’instruction
new String[0].
Cet état initial peut se représenter comme suit.
Pile Tas
String[]
Mémoire privée de main
args : •
Pile Tas
L’exécution se poursuit avec la ligne 14. Il s’agit d’une affectation. La partie droite doit être
calculée, puis le résultat stocké dans la mémoire privée de la varaible x. La partie droite est un appel
de méthode : le calcul se déroule en qautre étapes, comme expliqué ci-dessus.
— Étape 1 : calcul des valeurs des paramètres : premier paramètre x, valeur : 127. Deuxième
paramètre 12, valeur : 12.
— Étape 2 : allocation d’une mémoire privée pour la méthode min avec les deux paramètres
(nb1 et nb2) et leurs valeurs calculée précedemment. Cela nous conduit à l’état suivant.
Pile Tas
Pile Tas
Pile Tas
Le if est terminé, il reste à exécuter la ligne 9. Celle-ci renvoie la valeur de res, qui est 12.
L’étape 3 du calcul de l’appel de la méthode est terminé.
— Étape 4 : la mémoire privée de min est effacée, rendue disponible pour la mémoire privée
d’une autre méthode. La valeur 12 précédemment calculée est renvoyée.
Pile Tas
String[]
On revient alors à la ligne 14 dont l’exécution a commencé depuis un moment et qu’il s’agit de
terminer. L’appel de min a renvoyé la valeur 12. C’est cette valeur qui est stockée dans la mémoire
privée de x.
Pile Tas
String[]
8 }
9 return x;
10 }
11 public static void main(String[] args){
12 int x;
13 x = 127;
14 x = min(x,12);
15 x = x *x;
16 System.out.println(x);
17 }
18 }
Le changement de nom de la variable ne change rien à l’exécution : les deux déclarations (lignes
3 et 12) conduisent à deux allocations de mémoire différentes dans les deux mémoires privées des
deux méthodes main et min. Ce sont deux espaces différents qui évoluent indépendament. Une
affectation dans la méthode main (exemple : ligne 14) change le x de la mémoire privée de main et
une affectation dans la méthode min (exemple : ligne 5) change le x de la mémoire privée de min.
Nous n’allons pas détailler tous les états mémoires successifs, mais nous allons en donner deux.
Après exécution de la ligne 13, l’état est la suivant.
Pile Tas
Au cours de l’appel à min(x,12) (ligne 14), après calcul des valeurs des paramètres, allocation
d’une mémoire privée pour min avec les deux paramètres, déclaration de la variable locale x, évalua-
tion de la condition du if (résultat : false) et exécution du cas else (ligne 7), l’état de la mémoire
est le suivant.
Pile Tas
On voit ici qu’il y a deux variables x avec deux valeurs différentes (127 et 12).
Nous allons voir un nouvel exemple où une méthode est appelée dans le corps d’une autre mé-
thode, elle-même appelée par le main.
Le début de l’exécution du programme est identique à celui des deux exemples déjà vus. Après
exécution de la ligne 21, l’état de la mémoire est :
Pile Tas
Ligne 22, il faut exécuter l’appel min3(x,12,x+1). L’étape 1 consiste à calculer les valeurs
des trois paramètres. Ce sont respectivement 127, 12 et 128. Une mémoire privée pour min3 est alors
allouée avec les mémoires privées des trois paramètres contenant leurs valeurs respectives.
Pile Tas
L’étape 3 consiste à exécuter les instructions de min3 en commençant par la ligne 12. Une mé-
moire privée est allouée pour la varaible local res.
Pile Tas
A la ligne suivante (ligne 13), il y aune affectation. Il faut commencer par calculer l’appel min2(nb2,nb3).
L’étape 1 du calcul coniste à chercher les valeurs des deux paramètres. Le premier paramètre vaut ce
qu’il y a dans le paramètre nb2, c’est-à-dire 12. Le second vaut ce qu’il y a dans nb3, c’est-à-dire
128. L’étape 2 alloue une mémoire privée pour la méthode min2 contenant les deux mémoires privées
des paramètres de min2, qui sont nb1 et nb2.
Il convient ici de bien distinguer les paramètres de min3 et ceux de min2, bien que certains
d’entre eux aient le même nom. A la ligne 13, comme on est dans le corps de min3, nb2 désigne le
paramètre de ce nom dans min3 et va chercher sa valeur dans la mémoire privée de min3. Quant à
nb3, c’est un nom qui n’apparaît que dans min3, il n’y a pas de doute possible.
Pile Tas
Dans cette situation, il y a deux nb1 avec deux valeurs différentes et de même deux nb2 avec
deux valeurs différentes. Dans cette situation, il y a 3 mémoires privées de méthodes correspon-
dant aux trois méthodes différentes du programme. On va commencer l’étape 3 du calcul de l’appel
min2(nb2,nb3). On est également en cours de calcul de l’étape 3 de l’appel min3(x,12,x+1).
La ligne 3 s’exécute et alloue une mémoire privée pour la variable locale de min2.
Pile Tas
Il y a à présent deux variables locales qui s’appellent res. Lorsqu’on exécute une instruction,
il n’y a pas de question à se poser : ce n’est que celle qui est le plus haut dans la pile qui peut être
utilisée. A un moment donné, même s’il y a plusieurs mémoires privées de méthodes dans la pile,
seule celle qui est au plus haut peut être utilisée. Les autres mémoires sont en attente et redeviendront
actives ultérieurement en redevenant la mémoire du sommet de la pile.
Au moment où il y a trois mémoires privées de méthodes dans la pile, il y a également trois lignes
de code en cours d’exécution, une dans chacune des trois méthodes. Au point relaté ci-dessus, la ligne
4 (mehode min2 va être exécutée, la ligne 13 (méthode min3) est commencée et elle est en attente du
résultat renvoyé par min2 pour s’achever et la ligne 22 (méthode main) est commencée et en attente
du résultat de min3 pour s’achever. A ce point, les instructions de min3 vont toutes s’exécuter avant
que la ligne 13 puisse s’achever. Et au moment où la ligne 13 sera terminée, les lignes 14 à 17 seront
exécutées avant que la ligne 22 puisse se terminer.
Pile Tas
Le if (lignes 4 à 8) est terminé. La ligne 9 doit être exécutée : elle retourne la valeur de res,
c’est-à-dire 12. La mémoire privée de min2 est libérée et la mémoire privée de min3 redevient le
sommet de la pile.
Pile Tas
La ligne 13 peut enfin se terminer. La valeur 12 retournée par min2 est mise dans res.
Pile Tas
Ligne 14, il y a comparaison de nb1 (valeur 127) et de res (valeur 12). 127 n’est pas plus petit
que 12, la condition est fausse. Comme il n’y a pas de cas else dans ce if, l’exécution du if est
terminée et l’on passe à la ligne suivante. Celle-ci (ligne 17) renvoie la valeur de res : 12. La mémoire
privée de min3 est libérée et la mémoire privée de main redevient le sommet de la pile.
Pile Tas
String[]
Pile Tas
String[]
Mémoire privée de main
args : •
x : 12
La ligne 23 commence par le calcul de x*x qui vaut 144. Cette valeur est mise dans x. Puis ligne
24 elle est affichée à l’écran. Ainsi se termine l’exécution du programme.
12 }
13 }
Pile Tas
String[]
int[]
0 4
Mémoire privée de main
1 8
args : •
2 12
x : 4
tab : •
A la ligne suivante, il y a un appel de méthode. l’étape 1 consiste à calculer les valeurs des pa-
ramètres. La valeur de x est 4 et celle de tab est l’adresse du tableau d’int dans la tas, matérialisée
par une flèche vers la première case de ce tableau. La valeur qui sera donnée au paramètre sera cette
adresse.
L’étape 2 de l’exécution de l’appel de méthode est la création d’une mémoire privée comportant
les paramètres valint et valtab.
Pile Tas
String[]
Mémoire privée de changeXEtTab
valint : 4
valtab : •
int[]
0 4
Mémoire privée de main
1 8
args : •
2 12
x : 4
tab : •
On voit dans cette représentation de la mémoire que passer un tableau en paramètre ne crée pas
un nouveau tableau. Ce n’est pas le tableau lui-même qui est recopié dans la mémoire privée de la
méthode mais seulement son adresse, l’emplacement de la mémoire privée du tableau dans le tas.
À partir de là, l’étape 3 consiste à exécuter les instructions de la méthode. Ligne 10 : le contenu
de valint est multiplié par 10, ce qui donne 40. Ce résultat est stocké dans valint.
Pile Tas
String[]
Mémoire privée de changeXEtTab
valint : 40
valtab : •
int[]
0 4
Mémoire privée de main
1 8
args : •
2 12
x : 4
tab : •
Ligne 11 : le contenu de valtab[0] ets multiplié par 10 ce qui donne 40. Le résultat est stocké
dans valtab[0].
Pile Tas
String[]
Mémoire privée de changeXEtTab
valint : 40
valtab : •
int[]
0 40
Mémoire privée de main
1 8
args : •
2 12
x : 4
tab : •
En changeant valtab[0] dans changeXEtTab on change aussi tab[0] dans main car
valtab[0] et tab[0] désigne le même emplacement dans la mémoire, dans le tas.
Dans le cas de x, l’affectation change x dans l’esapce privé de changeXEtTab, dans la pile, ce
qui ne peut avoir aucun effet sur les instructions exécutées dans la méthode main.
La méthode étant à présent terminée, sa mémoire privée est libérée.
Pile Tas
String[]
int[]
Mémoire privée de main
0 40
args : •
1 8
x : 4
2 12
tab : •
L’exéuction de la ligne 5 est terminée, il faut à présent exécuter la ligne 6. Elle affiche le contenu
de la variable x, c’est-à-dire 4. Puis ligne 7, c’est tab[0], c’est-à-dire 40 qu’il faut afficher.
On voit donc que l’exécution de la méthode n’a rien changé à la variable de type int mais qu’elle
a modifié le tableau passé en paramètre.
10.2.1 Terminologie
On dit qu’un paramètre est passé par copie si la valeur qu’il contient est écrite dans un nouvel
emplacement en mémoire. On dit que ce paramètre est passé par adresse ou par référence si la valeur
n’est pas copiée mais son adresse en mémoire est transmise au sous-programmme (méthode, fonction
ou préocédure appelée). En java, les types primitifs sont transmis par copie et les tableaux et objets
(comme les String par exemple) sont transmis par référence.
Dans d’autres langages, le programmeur a le choix du mode de passage des paramètres. C’est par
exmple le cas en C et en C++.
L’exécution commence par la ligne 11, la création d’un tableau à deux cases dans le tas et son
affectation à la variable tab déclarée sur cette ligne.
Pile Tas
String[]
char[]
Mémoire privée de main
0 ’a’
args : •
1 ’b’
tab : •
À la ligne 12, appel de méthode. Étape 1 : calcul des valeurs de tab et ’c’. Ce sont respective-
ment l’adresse du tableau dans le tas (la flèche vers le tableau) et le caractère ’c’. Étape 2 : création
d’une mémoire privée pour agrandir avec les paramètres et leurs valeurs respectives.
Pile Tas
A partir de là, l’étape 3 exécute les instructions du corps d’agrandir en commençant par la
ligne 3, déclaration de res, création d’un tableau à 3 cases et affectation de l’adresse de ce tableau à
res.
Pile Tas
String[]
Nous ne détaillerons pas l’exécution de la boucle (lignes 4 à 6), qui va recopier le contenu des
deux cases de tab dans res, ni celle de la ligne 7 qui va recopier le contenu de nv dans la dernière
case de res. A l’issue de ces exécutions, la mémoire est dans l’état suivant.
Pile Tas
String[]
A partir de là, la ligne 8 est exécutée. Elle consiste à recopier l’adresse de res dans tab. L’état
de la mémoire est le suivant.
Pile Tas
String[]
La méthode est terminée. Elle ne renvoit pas de résultat car c’est une méthode void. Étape 4 de
l’exécution : la mémoire privée de agrandir est libérée.
Pile Tas
String[]
char[]
0 ’a’
1 ’b’
Mémoire privée de main
args : • char[]
tab : • 0 ’a’
1 ’b’
2 ’c’
On voit que l’appel de la méthode n’a absolument pas changé le tableau tab de la méthode main,
que l’agrandissement n’a pas eu lieu et que le tableau à trois cases n’est pas accessible depuis main
faute d’un nom pour désigner son adresse.
Ce comportement est confirmé par l’exécution du programme.
res[tab.length]=nv;
return res;
}
public static void main(String[] args){
char[] tab = {’a’, ’b’};
tab = agrandir(tab,’c’);
for (int i=0; i<tab.length; i++){
System.out.print(tab[i]);
}
System.out.println();
}
}
L’exécution du programme :
Il n’existe pas à proprement parler de tableaux à deux dimensions en Java : ce qu’on appelle
tableaux à deux dimensions sont en fait des tableaux à une dimension qui contiennent dans chaque
cas un tableau à une dimension.
On définit un tableau de trois cases et dans chacune des trois cases il y a un tableau à deux cases.
tab[0] désigne un tableau à deux cases. On peut accéder à la première case de ce tableau en ajoutant
[0] après, ce qui nous donne tab[0][0].
Pile Tas
String[]
int[][]
0
1
1
int[]
0 1
Mémoire privée de main 1 2
args : •
tab : • int[]
0 3
1 4
int[]
0 5
1 6
Il est possible d’affecter le contenu d’une case du tableau tab à une variable de type int[] et
réciproquement. tab[0] désigne un tableau à une dimension.
Nous allons voir dans ce chapitre comment utiliser des tableaux pour représenter des données,
notamment quand le nombre d’éléments varie au fil de l’exécution et quand il faut enregistrer des
données de types différents pour représenter une certaine donnée.
1
11.1. UN PROBLÈME DE TAILLE CHAPITRE 11. TABLEAUX ET DONNÉES
Cette technique d’adaptation de la taille du tableau aux besoins est intéressante dans son principe
mais ne doit pas être utilisée à chaque opération d’ajout ou de suppression parce que les opérations à
réaliser sont trop coûteuses. D’une part l’opération de création du tableau (new) est de loin l’opération
qui prend le plus de temps. D’autre part, devoir recopier tous les noms d’un tableau à l’autre est
également long, surtout si le tableau est grand.
On préférera utiliser un tableau plus grand que nécessaire avec certaines cases vides. Les opéra-
tions de changement de tableau seront réservées au cas où il n’y a plus de cases vides et on rajoutera
d’un coup non pas une case, mais plusieurs cases (par exemple cent cases), dont certaines seront vides.
nb = Terminal.lireInt();
// recherche du numéro de case du nombre
while(indice<liste.length && liste[indice]!=nb){
indice=indice+1;
}
if (indice == liste.length){ // on n’a pas trouvé le nombre
System.out.println("Ce nombre n’est pas dans le tableau");
}else{ // le nombre a été trouvé en case indice
liste[indice]=Integer.MIN_VALUE;
System.out.println("nombre supprimé");
}
// ajouter un nombre
System.out.println("Quel nombre ajouter?");
nb = Terminal.lireInt();
// chercher la première case libre
indice = 0;
while(liste[indice] != Integer.MIN_VALUE){
indice = indice+1;
}
// remplir la case vide avec le nombre
liste[indice]=nb;
// affichage de la liste
for (int i=0; i<liste.length; i=i+1){
// ne pas afficher les cases vides
if (liste[i]!=Integer.MIN_VALUE){
System.out.print(liste[i]+ " ");
}
}
System.out.println();
}
}
Notons que la façon de supprimer un élément ne préserve pas l’ordre d’origine des éléments
restants : c’est le dernier élément du tableau qui prend la place de l’élément supprimé. Si l’ordre des
éléments dans la tableau est important, il faut procéder autrement : il faut décaler d’un cran à gauche
tous les éléments situés à droite de l’élément supprimé. Cela peut se faire facilement avec une boucle
for.
main. Cette affectation change donc le contenu de la variable liste de main. La deuxième affectation
change le paramètre nb d’ajouter, mais pas la variable nbelem de main qui reste à 0.
La case du tableau, en mémoire, est dans le tas alors que le paramètre nb et la variable nbelem
sont dans la pile et donc locales à leurs méthodes respectives.
Le dession suivant illustre l’état de la mémoire à la fin de l’exécution de la méthode ajouter.
Pile Tas
Comment faire en sorte que ajouter et main puisse partager un nombre d’éléments dans un espace
mémoire commun ? Il faut que cet espace mémoire soit dans le tas. Une façon de procéder consiste à
utiliser un tableau d’entier à une case pour enregistrer le nombre d’éléments de la liste. Cette case de
tableau est partagée entre les différentes méthodes (ici, main et ajouter).
Voici le code que cela donne.
public class SDD3Ter{
public static void ajouter(int[] liste, int[] nb, int elem){
liste[nb[0]]=elem;
nb[0]=nb[0]+1;
}
public static void afficher(int[] liste, int[] nb){
if (nb[0] == 0){
System.out.print("La liste est vide");
}
for (int i=0; i<nb[0]; i=i+1){
System.out.print(liste[i]+" ");
}
System.out.println();
}
public static void main(String[] args){
int[] liste = new int[100];
int[] nbelem = {0};
ajouter(liste,nbelem,474);
afficher(liste,nbelem);
}
}
}
public static void afficher(int[] liste, int[] nbelem){
for (int i=0; i<nbelem[0]; i=i+1){
System.out.print(liste[i]+ " ");
}
System.out.println();
}
public static void main(String[] args){
// créer une liste avec 100 cases
int[] liste = new int[100];
int[] nbelem = {0};
int nb;
// mettre les nombres en début de liste
inserer(liste,nbelem,474);
inserer(liste,nbelem,317);
inserer(liste,nbelem,215);
inserer(liste,nbelem,700);
inserer(liste,nbelem,999);
inserer(liste,nbelem,895);
// affichage de la liste
afficher(liste,nbelem);
// supprimer un nombre
System.out.println("Quel nombre ajouter?");
nb = Terminal.lireInt();
inserer(liste,nbelem,nb);
// affichage de la liste
afficher(liste,nbelem);
}
}
Exécution du programme :
> java SDD4
Quel nombre ajouter?
330
215 317 330 474 700 895 999
On peut améliorer l’efficacité de la recherche de la case où l’élément doit être inséré en faisant
une recherche dichotomique à la place de la recherche séquentielle. Cette optimisation n’est utile que
pour des gros tableaux et elle rend l’opération d’insertion plus complexe. C’est pourquoi nous ne la
donnons pas ici.
inserer(liste,nbelem,474);
inserer(liste,nbelem,317);
inserer(liste,nbelem,215);
inserer(liste,nbelem,700);
inserer(liste,nbelem,999);
inserer(liste,nbelem,895);
// affichage de la liste
afficher(liste,nbelem);
// supprimer un nombre
System.out.println("Quel nombre supprimer?");
nb = Terminal.lireInt();
if (supprimer(liste,nbelem,nb)){
System.out.println("Elément supprimé");
}else{
System.out.println(nb + " non trouvé dans le tableau.");
}
// affichage de la liste
afficher(liste,nbelem);
}
}
Si l’on a plusieurs informations du même type Java pour caractériser une certaine entité, on peut
utiliser un tableau à deux dimensions. Par exemple, si l’on veut représenter le nom et le prénom d’une
liste de personne, les deux informations sont de type String. On peut utiliser un tableau avec une
colonne pour les noms, une autre colonne pour les prénoms et une ligne pour chaque personne.
nom prénom
personne 1 0 nom 1 prénom 1
personne 2 1 nom 2 prénom 2
... 2 ... ...
Si maintenant les deux informations sont de types différents, on ne peut pas utiliser un tableau à
deux dimensions car toutes les cases d’un tableau à deux dimensions contiennent des informations du
même type.
Supposons que l’on veuille caractériser une personne avec son nom et son année de naissance. Le
nom est un String et l’année est un int. On peut utiliser deux tableaux distincts au sens de Java, mais
avec le même type de gestion que si ces deux tableaux différents étaient les deux colonnes d’un même
tableau. C’est-à-dire qu’on utilisera le numéro de case (numéro de ligne) pour relier le nom et l’année
de naissance d’une personne données.
Si l’on trouve le nom d’une personne dans la case numéro n du tableau des noms, alors on sait que
l’année de naissance de la personne dont c’est le nom est dans la case numéro n du tableau des années
de naissance.
nom année
personne 1 0 nom 1 0 année 1
personne 2 1 nom 2 1 année 2
... 2 ... 2 ...
On peut utiliser le même principe pour gérer plus de deux tableaux ainsi que des tableaux avec
des dimensions différentes. On peut par exemple coordonner un tableau à deux dimensions pour les
noms et prénoms avec un tableau à une dimension pour les années de naissance.
Dans ce chapitre, nous allons aborder l’utilisation d’objets de deux classes prédéfinies de Java
d’usage très courant. La première, nous l’utilisons depuis longtemps déjà, c’est la classe String.
En java, les chaînes de caractères sont des objets. Nous allons apprendre dans ce chapitre à mieux les
utiliser. La seconde classe s’appelle ArrayList. Les objets de cette classes sont équivalent à des
tableaux, mais sont plus agréables à utiliser que les tableaux grâce aux méthodes qu’elle fournit.
1
12.3. CHAÎNES ET TABLEAUX CHAPITRE 12. UTILISER DES OBJETS
Cela crée la chaîne "bonjour". Si l’on n’a plus besoin du tableau après la création de la chaîne,
on n’a pas besoin d’utiliser une variable pour désigner ce tableau, on peut écrire directement la créa-
tion :
String s = "bonjour";
Terminal.ecrireIntln(s.length());
L’appel s.length() va appeler la méthode length sur l’objet contenu dans la variable s. Le
résultat est 7 puisqu’il y a 7 caractères dans "bonjour".
On appelle souvent les méthodes en mettant un nom de variable avant le point, mais on peut mettre
n’importe quelle expression qui calcule un objet du bon type. On peut appeler length non seulement
sur une variable mais sur d’autres sortes d’expressions calculant un objet String :
— une chaîne entre guillement : "bonjour".length()
— une chaîne créér par new : (new String()).length()
— le résultat d’une concaténation : (s + "qsd").length()
— etc.
— il n’y a pas une syntaxe spécifique pour accéder directement aux caractères d’une chaîne de
caractère.
Les chaînes de caractères sont le premier exemple d’objet que nous voyons en cours. Les types
des objets sont comme les tableaux des types références, c’est à dire que les variables de ces types
contiennent l’adresse des objets ou des tableaux en mémoire.
A noter : il n’y a pas de méthode ni d’autre moyen de changer un caractère dans une chaîne : une
fois la chaîne créée, on ne peut pas la modifier. Pour obtenir un résultat plus ou moins équivalent à un
changement de caractère, il faut créer une nouvelle chaîne en concaténant des portions de la chaîne
originale avec le caractère différent.
Dabs le code suivant, s est à null. Le programme compilera, mais on aura une erreur à l’exécution :
une NullPointerException due à l’application de la méthode length() à une variable qui
vaut null.
public class NPE {
public static void main(String[] args){
String s= null;
Terminal.ecrireIntln(s.length());
}
}
Concrètement, null est utilisé pour dire qu’un objet n’est pas présent (imaginez par exemple
qu’on représente une personne par un prénom, un second prénom, et un nom de famille, soit trois
Strings. Si la personne n’a pas de second prénom, il pourra être initialisé à null).
Lorsque l’on crée un tableau de chaînes, la valeur null est la valeur par défaut placée dans toutes
les cases. Dans l’état actuel de vos connaissance, c’est le cas où vous risquez le plus de rencontrer la
valeur null.
Il est possible d’utiliser null comme valeur dans une affectation et de la tester dans un test
d’égalité ou de différence. En revanche, on ne peut pas appeler de méthodes sur cette valeur. Par
exemple null n’a pas de longueur, pas de premier caractère, etc.
public class VNI{
public static void main(String[] args){
String s = "truc";
if (s!=null){
Terminal.ecrireIntln(s.length());
}
s = null;
if (s!=null){
Terminal.ecrireIntln(s.length());
}
}
}
Si l’on représente par un dession l’état de la mémoire après les affectations des trois chaînes, cela
donne le dessin suivant.
Pile Tas
String[]
Mémoire privée de main
args : •
s1 : •
String
s2 : •
tati
s3 : •
b1 : false
String
b2 : true
tati
Si l’on veut réaliser une comparaison du contenu des chaînes et non plus de leur adresse, il faut
utiliser une méthode. Pour une comparaison d’égalité, c’est la méthode equals qu’il faut appeler.
Elle renvoie une valeur booléenne. Elle compare deux chaînes : l’une est l’objet sur lequel on appelle
la méthode, l’autre est passée en paramètre. s1.equals(s2) fait un test d’égalité du contenu entre
les deux chaînes s1 et s2. Elles sont considérées comme égales si elles ont la même taille et les
mêmes caractères dans le même ordre.
Le programme suivant illustre l’utilisation de equals. Notons au passage que l’on peut insérer
un caractère guillement dans une chaîne de caractères en le faisant précéder du caractère \ (voir la
variable s2).n
public class ExChaine{
public static void main(String[] args){
String s1 = "Bonjour";
String s2 = "C’est \"bien\" ";
String s3;
String[] ts = {"Paul", "Andre", "Jacques", "Odette"};
Terminal.ecrireStringln(s2);
Terminal.ecrireString("Entrez une chaine: ");
s3=Terminal.lireString();
Terminal.ecrireStringln("s3: " + s3);
s2 = "Bon";
s3 = s2 + "jour";
if (s1 != s3){
Terminal.ecrireStringln("Bizarre: s1 n’est pas egal a s3!");
Terminal.ecrireStringln("s1: " + s1 + ":");
Terminal.ecrireStringln("s3: " + s3+ ":");
}
if (s1.equals(s3)){
Terminal.ecrireStringln("s1 est quand meme egal a s3!");
}
if (!s1.equals(s3)){
Terminal.ecrireStringln("s1 n’est toujours pas egal a s3!");
}
}
}
La méthode compareTo fait une comparaison d’ordre selon le code unicode de chaque caractère.
Cela correspond à l’ordre alphabétique uniquement pour les caractères sans accent. Elle renvoie un
entier qui est 0 en cas d’égalité, un entier positif si l’objet est plus grand que la paramètre et un entier
négatif si c’est le paramètre qui est plus grand.
0 1 2 3
La tableaux args vaut
¨un¨ ¨12¨ ¨56¨ ¨trois¨
Si l’on veut transformer une de ces chaînes en un entier, il faut utiliser une fonction de conversion.
Pour convertir une valeur de type double, il faut utiliser la méthode Double.parseDouble et
pour le type boolean, la méthode Boolean.parseBoolean.
Pour convertir dans l’autre sens, un int en chaîne, le plus simple est d’utiliser l’opérateur de conca-
ténation : ""+12 (pour les doubles ""+12.3, pour les booléens ""+true). On concatène la chaîne
vide avec la valeur à convertir.
12.10.1 Utilisation
Les objets se manipulent essentiellement à travers des méthodes. Les méthodes sont des procé-
dures ou des fonctions qui sont appelées ń sur ż l’objet. Par exemple, la méthode size(), qui est une
fonction, retourne la longueur d’une ArrayList. On pourra l’appeler de la manière suivante :
int long= maListe.size();
En règle générale, pour appeler une méthode m sur un objet o, on écrit o.m(). Donc, dans notre
exemple, on appelle la méthode size sur l’objet maListe.
La liste des méthodes disponible pour un objet donné est fixe, et dépend du type (plus exactement
de la classe) de l’objet. Notre objet maListe est de classe ArrayList, il dispose donc des mé-
thodes d’ArrayList. La liste des méthodes d’une classe fait partie de la documentation de celle-ci,
et explicite ce qu’il est possible de faire avec un objet de la classe en question.
Nous allons donc examiner quelques unes des méthodes disponibles sur les ArrayList. 1 , pour
négliger un certain nombre de problèmes qu’il est un peu tôt pour aborder. L’approximation que nous
faisons est bien évidemment compatible avec la ń vraie ż spécification.
Le minimum vital
Les méthodes qui suivent permettent d’obtenir la même chose qu’avec un tableau normal, mais en
gagnant, en plus, la possibilité d’ajouter une nouvelle case, ou de supprimer une case existante.
Dans le texte qui suit, Type correspond au type des éléments de l’ArrayList. Pour une ArrayList
de String, par exemple, on remplacera Type par String
int size() : fonction qui renvoie la longueur d’une ArrayList ; La fonction booléenne isEmpty
permet de savoir si une liste est vide.
Type get(int i) renvoie l’entrée de la case numéro i. Comme pour les tableaux, les cases des ArrayList
sont numérotées en commençant à 0. Le type de l’objet retourné est celui précisé lors de la
création de l’ArrayList. Pour nous ce sera donc String, Double ou Integer. À
partir de java 1.5, java ń sait ż convertir un Integer ou un Double en int ou double. La
fonction suivante permet donc de calculer la somme des éléments d’une ArrayList d’en-
tiers :
1. Dans ce cours, nous avons simplifié les en-têtes réels des méthodes de la classe ArrayList
Et un peu plus
La classe ArrayList est très riche. Voyons donc quelques méthodes supplémentaires.
boolean contains(Type element) renvoie vrai si la liste contient la valeur element.
int indexOf(Type element) renvoie la position de element dans la liste, et -1 s’il n’y apparaît pas.
add(int i, Type element) ajoute la valeur element à la position i. i doit être inférieure ou égale à
size() (pourquoi inférieure ou égale et non pas simplement inférieure stricte comme pour
get ?). La fin du tableau est décalée (l’ancien élément en position i passe en position i +
1, etc.)
clear() vide la liste.
addAll(ArrayList<Type> l1) l’appel
2. Il y a une ambiguïté si on appelle l.remove(5) sur une ArrayList<Integer>. S’agit-il de supprimer la
valeur 5, ou la valeur de la case numéro 5 ? Si on passe un int, c’est la première méthode remove qui sera appelée (on
supprime l’élément qui est dans la case d’indice 5) ; si on appelle l.remove(new Integer(5), c’est la valeur 5 qui
sera supprimée.
l1.addAll(l2)
ajoute tous les éléments de l2 à la fin de l1 (concatène donc l1 et l2. l1 est modifiée, l2 ne l’est
pas.
retainAll l’appel
l1.retainAll(l2)
enlève tous les éléments de l1 qui ne sont pas dans l2.
removeAll l’appel
l1.removeAll(l2)
enlève tous les éléments de l1 qui sont dans l2. Après l’appel, il ne reste plus d’élément de l2
dans l1.
import java.util.ArrayList;
/**
*Une simulation de caisse enregistreuse.
*/
public class Caisse {
public static void main(String args[]) {
ArrayList<String> nomsArticles= new ArrayList<String>();
ArrayList<Double> prixArticles= new ArrayList<Double>();
boolean fin= false;
while (! fin) {
Terminal.ecrireStringln("votre choix : ");
Terminal.ecrireStringln("1: ajouter un article");
Terminal.ecrireStringln("2: supprimer un article");
Terminal.ecrireStringln("3: terminer et afficher le total");
int rep= Terminal.lireInt();
if (rep == 1) { // ajout d’un produit
Terminal.ecrireString("nom de l’article :");
String nom= Terminal.lireString();
Terminal.ecrireString("prix de l’article :");
double prix= Terminal.lireDouble();
nomsArticles.add(nom);
prixArticles.add(prix);
afficherTicket(nomsArticles, prixArticles);
} else if (rep == 2) {
V. Aponte
Cnam
7 décembre 2012
Sous-programmes : outil de découpage/structuration
• Initialisation :
1. lire une liste de noms de matières,
2. lire une note par matière,
• Boucle avec menu d’opérations :
1. afficher le bulletin des notes
2. chercher la note d’une matière
3. modifier une note
4. finir
Interface texte : gestion de notes
Nombre de matieres? 4
Une matiere? Maths
Une matiere? SVT
Une matiere? Info
Une matiere? Anglais
Note de Maths ? 10
Note de SVT ? 11
Note de Info ? 12
Note de Anglais ? 13
main
Initialisation Opérations
Initialisation
notes
matières
String [] double []
lireTab(int) lireTabNotes(String[])
double
lireDoub(inf, sup)
Sous-tâches pour les opérations
Opérations
fin
void modifier
menu+ afficher opAfficheBulletin(mat,not)
bulletin note
choix
lecture
validée entier void
opChercheNote(mat,not)
int
void
lireEnt(inf, sup)
opChangeNote(mat,not)
Nous détaillons :
• entêtes des méthodes par sous-tâche identifiée,
• commentaires précis de ce qu’elle doit faire + ses hypothèses.
⇒ a besoin de :
• calcul moyenne tableau notes :
Important :
• préciser conditions indispensables au bon fonctionnement des
méthodes ⇒ dans commentaires
• ex : le calcul de la moyenne se fait sur un tableau non vide ;
• cela nous aide à penser par la suite à les garantir lors des appels
• ⇒ appel avec tableau non vide
⇒ a besoin de :
• chercher l’indice d’une chaîne dans un tableau de chaînes
Autres :
• afficher le menu d’opérations et lire un choix,
/* Affiche les operations du menu
* lit un choix (valide) de l’utilisateur
* renvoi ce choix en resultat
*/
static int afficheMenuEtChoix()
En pratique, on se contente de :
• savoir bien expliquer comment représenter les données,
• comment s’en servir pour traiter le problème,
• et quelles sont les méthodes à écrire,
⇒ a besoin de lireDou.
Méthode lecture double entre inf et sup
⇒ a besoin de indiceMat.
Indice d’une matière