Vous êtes sur la page 1sur 179

Chapitre 1

Premier programme

1.1 Objectif du cours


Les objectifs du cours sont d’apprendre les bases de la programmation en utilisant le langage Java
comme support. Ces bases sont pratiquement communes avec l’écrasante majorité des langages de
programmation et nous éviterons d’utiliser ce qui est trop spécifique au langage Java.
Les notions étudiées seront les suivantes :
— variable, type, expression
— l’instruction conditionnelle (if)
— les boucles (for, while)
— les tableaux
— les sous-programmes (méthode, fonction, procédure)
— en conclusion du cours, une ouverture vers la programmation objet avec l’utilisation d’objets
de deux classes prédéfinies : String et ArrayList.
Le but du cours est de vous donner non seulement des connaissances, mais également une compé-
tence : celle de réaliser concrètement de petits programmes java utilisant les constructions énumérées
ci-dessus et de les exécuter sur un ordinateur.

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.3 Programme source et programme cible


Chaque programme a deux formes différentes : une forme qui est le programme tel qu’il est écrit
par le programmeur et l’autre est le programme tel qu’il est exécuté par l’ordinateur. Ces deux formes
sont différentes : le programme que l’on écrit a une forme textuelle. On peut le lire : il contient des

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.

1.3.1 Traduction : compilation et interprétation


Pour passer d’une forme à l’autre, il faut une traduction qui est exécutée automatiquement par
un programme. Il existe deux sortes de traduction : la traduction réalisée une fois pour toute par un
programme appelé un compilateur ou la traduction réalisée à chaque exécution par un logiciel appelé
interpréteur.
Le compilateur est analogue à la traduction d’un livre alors que l’interpréteur est analogue à la
traduction simultanée utilisée pour traduire en direct les propos tenus dans une langue étrangère.
Le langage Java que nous utilisons mixe les deux procédés avec utilisation d’un compilateur puis
d’un interpréteur.
Dans cette histoire, le langage des programmes écrits par les programmeurs s’appelle le langage
source de l’ordinateur. C’est le langage Java qui est unique pour tous les ordinateurs du monde. Le
langage des programmes exécutés s’appelle le langage cible et il est différent pour chaque type de
processeur, d’architecture et de système d’exploitation employé.
Dans le cas de Java, il y a un troisième langage qui est celui des programmes compilés par le
compilateur java. On l’appelle langage intermédiaire, langage de la machine virtuelle Java. Il est
unique pour tous les ordinateurs mais les machines ne savent pas l’exécuter directement. C’est pour
cela qu’il y a besoin d’un interpréteur qui interprète le code intermédiaire : la machine virtuelle.

1.3.2 Etapes pour la création d’un programme


La création de programmes nécessite l’utilisation de différents outils qui peuvent soit être des ou-
tils séparés, soit des outils regroupés dans un unique logiciel appelé environnement de développement
intégré (IDE en anglais).
Il y a au minimum trois outils : un éditeur de texte, un compilateur et un interprèteur.
L’éditeur de texte permet de créer le fichier contenant le programme source. Il s’agit d’une sorte de
traitement de texte mais qui ne comporte pas d’élément de mise en page car le but n’est pas d’imprimer
un texte. Le format de fichier utilisé est le format texte brut, celui des fichiers .txt en windows. Dans
le cas des programmes sources java, on n’utilise pas cette extension .txt mais l’extension .java. Cette
extension doit être utilisée quel que soit le système d’exploitation (windows, Mac OS, Linux).
Le compilateur prend en entrée le fichier texte contenant le programme source. Si le programme
source est correct, le compilateur crée un ou plusieurs fichiers binaires contenant le programme en
langage intermédiaire. Ces fichiers ont l’extension .class.
L’interpréteur prend en entrée un fichier contenant du code intermédiaire et exécute le code cor-
respondant. Pour ce faire, il traduit une ligne de code, puis l’exécute immédiatement avant de passer
à la suivante.
Pour ce cours, nous vous conseillons d’utiliser un environnement intégré, avec deux possibilités :
— utiliser un environnement pour débutant : drjava. Très facile à utiliser, il vous premettra de
démarrer rapidement et sans tracas. Mais il se révèlera limité par la suite. Il suffit amplement
pour le cours NFA031.
— utiliser un environnement professionnel : Eclipse. Il est plus difficile à prendre en main. Il a
de nombreuses fonctionnalités.

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.

1.4 Premier programme


1.4.1 Texte du programme
Le programme a pour but de calculer et afficher la conversion en dollars d’une somme en euros
saisie au clavier.

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);
}
}

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.

1.4.2 Structure générale d’un programme Java


Ce programme a un squelette identique à tout autre programme Java. Le voici :

Listing 1.1 – (pas de lien)


public class ... {
public static void main (String[] args) {
....
}
}

— 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 ...

1.4.3 comprendre le code

Voyons ligne par ligne le sens des instructions du programme.


double euros; déclarer le nom euros comme un nom utilisé pour désigner un nombre à vir-
gule qu’on ne connaît pas au moment où l’on écrit le programme. Lors de l’exécution du programme,
viendra un moment où ce nombre sera connu. On appelle variable un tel nom. Le mot-clé double
désigne en Java une approximation des nombres réels codée sur 8 octets.
double dollars; déclarer la variable appelée dollars destinée à contenir un nombre à virgule.
System.out.println("Somme en euros? "); sert à afficher à l’écran le texte donné
entre guillemmets (Somme en euros ?: ). Les guillemets ne sont pas affichés, seulement ce qu’il
y a dedans. Cette instruction est un appel d’une méthode prédéfinie appelée System.out.println.
euros = Terminal.lireDouble(); est une instruction qui sert à deux choses :
— Terminal.lireDouble() est un appel de méthode qui permet de lire un nombre à vir-
gule au clavier.
— le reste de l’instruction sert à associer le nom euros et le nombre lu au clavier. A partir de
l’exécution de cette ligne, le nom euros désigne ce nombre.
dollars = euros * 1.118;
— euros * 1.118 décrit un calcul, à savoir la multiplication du nombre désigné par le nom
euros et le nombre 1.118 (un nombre à virgule en notation américaine où le point remplace la
virgule). Le signe * désigne la multiplication.
— le reste de l’instruction sert à associer le nom dollars au résultat du calcul. A partir de l’exécu-
tion de cette ligne, dollars désigne ce résultat.
System.out.println("La somme en dollars: "); affiche le texte donné entre guille-
mets. Cela ressemble au texte de la ligne System.out.println("Somme en euros? ");
sauf que c’est la méthode System.out.print qui est appelée au lieu de System.out.println. Ces deux mé-
thodes servent à afficher du texte à l’écran, cependant la seconde passe à la ligne après cet affichage
mais pas la première.
System.out.println(dollars); affiche à l’écran le nombre à virgule associé au nom
dollars. Notez qu’il n’y a pas de guillemets ici. S’il y avait des guillemets, ce serait le mot dollars qui
s’afficherait et pas le nombre associé à cette variable.

4 c
NFA031 CNAM 2016
CHAPITRE 1. PREMIER PROGRAMME
1.5. COMPILATION ET EXÉCUTION DU PROGRAMME

1.5 Compilation et exécution du programme


1.5.1 Compilation du programme
Lors d’une première étape, le programme a été tapé et sauvé dans un fichier texte appelé Conver-
sion.java. Il faut ensuite compiler ce programme.
Il est possible d’appeler le compilateur en ligne de commande dans un terminal ou une fenêtre de
commandes windows. Cette commande sera la suivante :

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.

1.5.2 Erreurs de compilation


Voyons un exemple de programme comportant des erreurs.
public classe Bonjour {
public static void main (String[] args) {
System.out.println("Bonjour tout le monde !"*2);
}
}

Ce code contient deux erreurs :


— erreur de syntaxe : le mot-clé class qui introduit le nom du programme a été changé en
classe.
— erreur de typage (sémantique) : le message "Bonjour tout le monde !" est une chaîne
de caractères (type String) et en tant que tel ne peut être multiplié par 2.
Voyons ce qui ce passe lorsqu’on compile ce programme en ligne de commande :

> javac Bonjour.java


Bonjour.java:1: error: class, interface, or enum expected
public classe Bonjour {
^
Bonjour.java:2: error: class, interface, or enum expected
public static void main (String[] args) {
^
Bonjour.java:4: error: class, interface, or enum expected
}
^
3 errors

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.

> javac Bonjour.java


Bonjour.java:3: error: bad operand types for binary operator ’*’
System.out.println("Bonjour tout le monde !"*2);
^
first type: String
second type: int
1 error

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).

> java Bonjour


Bonjour tout le monde !2

1.6 Retour sur les instructions


Dans le programme Conversion, on a trois catégories d’instructions :
— des déclarations de variables. Les déclarations servent à donner un nom à une case mémoire,
de façon a pouvoir y stocker, le temps de l’exécution du programme, des valeurs. Une fois
qu’une variable est déclarée et qu’elle possède une valeur, on peut consulter sa valeur. Cette

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 Les variables


Les variables sont utilisées pour stocker les données du programme. A chaque variable correspond
un emplacement en mémoire, où la donnée est stockée, et un nom qui sert à désigner cette donnée tout
au long du programme.
Une variable doit être déclarée dans le programme. On peut alors consulter sa valeur ou modifier sa
valeur.

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.

le nom des variables

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.

syntaxe des déclarations de variables

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.

Execution des déclarations de variables

L’exécution d’un programme, rappelons-le, consiste en l’exécution successive de chacune de ses


instructions.
L’exécution d’une déclaration de variable consiste à réserver (on dit allouer) un espace mémoire
de taille suffisante pour contenir la valeur qui sera associée au nom de la variable. Dans le cas du type
double, il y aura 8 octets de réservés pour la variable euros.

1.7.2 Affecter une valeur à une variable


Une fois qu’une variable a été declarée, on peut lui donner une valeur, c’est à dire mettre une
valeur dans la case mémoire qui lui est associée. Pour cela, il faut utiliser l’instruction d’affectation
dont la syntaxe est

nom_variable = expression ;

par exemple x=2.5;

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;

Des valeurs du bon type

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 :+,-,*,/

Exécution d’une affectation


L’exécution d’une affectation se fait en deux temps :
1. On calcule le résultat de l’expression à droite de = (on dit évaluer l’expression). Si c’est une
valeur simple, il n’y a rien a faire, si c’est une variable, on va chercher sa valeur, si c’est une
expression avec des opérateurs, on applique les opérateurs à leurs opérandes. Cela nous donne
une valeur qui doit être du même type que la variable à gauche de =.
2. On met cette valeur dans l’emplacement mémoire associé à la variable à gauche de =.
C’est ce qui explique que l’on peut écrire le programme suivant qui n’a d’autre intérêt que d’illustrer
l’affectation) :
1 public class essaiVariable {
2 public static void main (String[] args) {
3 double x;
4 double y;
5 y=2.5;
6 x=y+5.1;
7 x=x+2.3;
8 }
9 }

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.

1.8 Les appels de méthodes prédéfinies


1.8.1 La méthode System.out.println
La ligne System.out.println("Somme en euros? "); donne l’ordre d’afficher à l’écran
le message somme en euros ?:. Elle le fait en utilisant un programme tout fait qui existe sans
qu’on ait à l’écrire. On appelle cela en Java une méthode. Ce programme s’appelle System.out.println.

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.

1.8.2 La méthode Terminal.lireDouble


La ligne suivante donne l’ordre de récupérer une valeur au clavier.

euros=Terminal.lireDouble();

Cette ligne est une affectation =. On trouve à droite du = un appel de méthode.


La méthode appelée s’appelle lireDouble et se trouve dans la classe Terminal.
Pour que cette ligne fonctionne, il faut avoir copié le fichier Terminal.java contenant cette
classe dans votre répertoire de travail, vous pourrez utiliser autant de fois que vous le désirez cette
méthode. Utiliser une méthode existante dans un programme s’appelle faire un appel de la méthode.
Le fait qu’il n’y ait rien entre les parenthèses indique que cette méthode n’a pas besoin que l’uti-
lisateur lui fournisse des informations. C’est une méthode sans arguments.
En revanche, le fait que cet appel se trouve à droite d’une affectation indique que le résultat de
son exécution produit un résultat (celui qui sera mis dans la variable euros). En effet, ce résultat est
la valeur provenant du clavier. C’est ce qu’on appelle la valeur de retour de la méthode.
Comme pour les arguments de la méthode, le fait que les méthodes retournent ou pas une valeur
est fixé par l’auteur de la méthode une fois pour toutes. Il a fixé aussi le type de cette valeur de
retour. Une méthode, lorsqu’elle retourne une valeur, retourne une valeur toujours du même type.
Pour lireDouble, cette valeur est de type double.
Lorsqu’ils retournent un résultat, les appels de méthode peuvent figurer dans des expressions.
Exemple : La méthode Math.min :prends 2 arguments de type int et retourne une valeur de type
int : le plus petit de ses deux arguments. Ainsi l’instruction x = 3 + (Math.min(4,10) +
2); donne à x la valeur 9 car 3+(4+2) vaut 9.
L’instruction x = 3 + (Terminal.lireInt() + 2); a aussi un sens. Elle s’exécutera de la
façon suivante : pour calculer la valeur de droite, l’exécution se mettra en attente qu’une ligne de texte
soit entrée (un curseur clignotant à l’écran indiquera cela). Une ligne de texte est une séquence de
touche frappées terminée par la touche de passage à la ligne (Enter ou Return ou Entrée). Dès que
l’utilisateur a entré une ligns, le calcul de Terminal.lireInt() se termine avec pour résultat
le nombre entier présent en début de la ligne entrée. Imaginons que l’utilisateur ait pressé 6. 3+6+2
donne 11. La valeur de x sera donc 11.

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.

1.9 Les méthodes prédéfinies d’entrée-sortie


Disons maintenant quelques mots sur les méthodes d’entrée-sortie.
Java est un langage qui privilégie la communication par interfaces graphiques. En particulier,
la saisie des données est gérée plus facilement avec des fenêtres graphiques dédiées, des boutons,
etc. Mais, pour débuter en programmation, il est beaucoup plus simple de programmer la saisie et
l’affichage des données au terminal : la saisie à partir du clavier et l’affichage à l’écran. Nous vous
proposons de suivre cette approche en utilisant la bibliothèque Terminal.

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.

Saisie avec Terminal : Se fait toujours par un appel de méthode de la forme :

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();

Méthodes de saisie dans Terminal :


— Terminal.lireInt()
— Terminal.lireDouble()
— Terminal.lireChar()
— Terminal.lireBoolean()
— Terminal.lireString()
Méthodes d’affichage :
Terminal contient également les méthodes d’affichage suivantes qui peuvent remplacer System.out.print
et System.out.println.
— Terminal.ecrireInt(n) ;
— Terminal.ecrireDouble(n) ;
— Terminal.ecrireBoolean(n) ;

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.

Affichage avec la bibliothèque System


La bibliothèque System propose les mêmes fonctionnalités d’affichage au terminal pour les types
de base que notre bibliothèque Terminal. Elles sont simples d’utilisation et assez fréquentes dans
les ouvrages et exemples que vous trouverez ailleurs. Nous les présentons brièvement à titre d’infor-
mation.
— System.out.print : affiche une valeur de base ou un message qui lui est passé en para-
mètre.
System.out.print(5); ---> affiche 5
System.out.print(bonjour); ---> affiche le contenu de bonjour
System.out.print("bonjour"); ---> affiche bonjour
Lorsque l’argument passé est un message ou chaîne de caractères, on peut lui joindre une autre
valeur ou message en utilisant l’opérateur de concaténation +. Attention si les opérandes de
+ sont exclusivement numériques, c’est leur addition qui est affichée !
System.out.print("bonjour " + 5 ); ---> affiche: bonjour 5
System.out.print(5 + 2); ---> affiche 7
— System.out.println : même comportement que System.out.print , mais avec
passage à la ligne en fin d’affichage.

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

2.1 Expressions, calculs et instructions


Une expression est un morceau de code Java, qui donne une façon de calculer une valeur d’un
certain type. L’exécution de cette expression consiste à réaliser ce calcul.
Une expression apparaît dans une instruction : la valeur calculée sert dans une instruction. Par
exemple, dans une affectation, l’expression est située à droite du signe =. Elle est utilisée pour donner
une valeur à la variable dont le nom est situé à gauche du =.
Les instructions simples, celles que nous avons utilisées dans notre premier programme, tiennent
sur une ligne. Dans ce programme, aucune expression n’est seule sur une ligne : elle est toujours
intégrée dans une instruction qui utilise la valeur calculée.
Reprenons ce programme et voyons quelles expressions il contient.
1 public class Conversion {
2 public static void main (String[] args) {
3 double euros;
4 double dollars;
5 System.out.println("Somme en euros? ");
6 euros = Terminal.lireDouble();
7 dollars = euros *1.118;
8 System.out.println("La somme en dollars: ");
9 System.out.println(dollars);
10 }
11 }

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.2 Types, valeurs et opérateurs


2.2.1 Type double : les nombres à virgule
Le type double est une approximation des nombres réels. Ces nombres sont codés sur 8 octets, 64
bits, ce qui ne permet pas de représenter tous les nombres réels qui sont beaucoup plus nombreux. On
sait qu’il y en a une infinité entre deux réels quels qu’il soient.
Les valeurs des double s’écrivent comme des nombres à virgules habituels, à ce détail près qu’on
utilise un point au lieu de la virgule, parce que c’est la notation utilisée par les anglo-saxons.
Voici des exemples de nombres à virgule écrits en Java : 1.2, 158.023, -1.5, 3.0
Les opérateurs utilisables sur les nombres à virgule sont les opérateurs arithmétiques notés +,-,*,/.
L’étoile sert à noter la multiplication, la barre oblique (slash) sert à noter la division.
Une chose importante à savoir à propos des nombres à virgule est que les calculs effectués sont
approximatifs. D’une part, il se peut que le résultat exact d’un calcul ne fasse pas partie des nombres du
type double. D’autre part, les algorithmes utilisés dans les calculs sont efficaces mais ne garantissent

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.

2.2.2 Type int : les nombres entiers


Le type int représente un intervalle des nombres entiers et contient tous les nombres de cet inter-
valle. Une valeur est codée sur 4 octets soit 32 bits. Ce type contient tous les nombres compris entre
-2147483648 et +2147483647 (−231 et 231 − 1). Notez que ces nombres ne sont pas très grands (de
l’ordre de 2,1 milliards).
Les valeurs du type sont les nombres notés avec la notation habituelle des entiers : 125, 0, -57.
Les opérateurs utilisables sont les opérateurs arithmétiques +, -, *, /. La division de deux entiers
donne un résultat entier : c’est une division euclidienne. Par exemple 10/3 vaut 3. Il existe un opérateur
noté % qui donne le reste de la division. Par exemple 10%3 vaut 1.

2.2.3 Type char : les caractères


Le type char représente les caractères, c’est-à-dire tout ce qu’on peut afficher dans un champ
textuel : des lettres, des chiffres, des espaces, des signes de poncutation, des caractères accentués.
Les valeurs du type sont notés entre apostrophes : ’a’, ’2’, ’@’.
En java les caractères sont codés sur 2 octets, soit 16 bits. A chaque caractère est associé un
numéro qui est la position de ce caractère dans la table unicode. Par exemple le caractère ’a’ a le
numéro 97, l’espace noté ’ ’ a le numéro 32. Il n’est pas utile de connaître la table par coeur.
Il n’y a pas d’opérateur utile sur les caractères.

2.2.4 Type boolean : les valeurs de vérité


Le type boolean représente les valeurs de vérité : vrai noté true et faux noté false en Java.
Ces valeurs servent à représenter notamment la véracité d’un énoncé. Par exemple : l’utilisateur
du programme est un homme. Cet énoncé est soit faux, soit vrai. Dans cet exemple, l’énoncé peut
avoir une valeur différente selon l’exécution du programme : le même programme peut être exécuté
tant par un homme que par une femme. Mais pour une exécution donnée, c’est soit l’un soit l’autre.
Les opérateurs les plus utiles sont le et logique noté &&, le ou logique noté || et le non logique
noté !.
Le type boolean a une grande utilité en programmation, et il désoriente souvent les débutants
car on n’a pas l’habitude de l’utiliser dans la vie courante. Nous reviendrons sur son usage dans les
prochains chapitres.

c
NFA031 CNAM 2016 3
2.3. LES DIFFÉRENTES SORTES D’EXPRESSION
CHAPITRE 2. EXPRESSIONS ET ALGORITHMES

2.2.5 Opérateurs de comparaison


En plus des opérateurs donnés pour chaque type et qui calculent des valeurs du type en question,
il existe des opérateurs qui permettent de comparer deux valeurs du même type et dont le résultat est
une valeur booléenne.
Il existe un opérateur pour tester si deux valeurs sont égales. Il est noté ==. Par exemple, on
peut écrire l’expression 1+2==2+1. Le calcul de cette expression donne comme résultat true, car
effectivement 1+2 et 2+1 sont égaux. En revanche, l’expression 1+2==100 donnera, au moment du
calcul, la valeur false, car 1+2 et 100 ne sont pas égaux.
Généralement, dans un programme, on utilise cela pour tester une valeur qui n’est pas connue au
moment de l’écriture, par exemple parce qu’elle dépend d’une entrée du programme. On écrira plutôt
des expressions comme euros==0, qui comportent une ou plusieurs variables.
L’opérateur pour tester que deux valeurs sont différentes s’écrit !=.
Les deux opérateurs == et != peuvent s’utiliser avec chacun des types double, int, char et boolean.
Il existe aussi des opérateurs d’ordre qui peuvent s’utiliser sur les valeurs des types double, int,
char mais pas sur les boolean. Ce sont les opérateurs inférieur noté <, inférieur ou égal noté <=,
supérieur noté > et supérieur ou égal noté >=.
On peut par exemple écrire l’expression x>10. Celle-ci vaut true ou false selon la valeur stockée
dans la variable x.
Pour les caractères, l’ordre utilisé est celui de la table unicode.

2.2.6 Type String


Le type String est celui des chaînes de caractères, qui sont des séquences ordonnées de caractères.
Chaque élément de la chaîne est un caractère du type char (lettre, chiffre, espace, signe de ponctuation,
symbole monétaire, etc).
Les valeurs du type sont notées entre guillemets : "coucou", "entrer une somme en
euros:?", "a" . . .
La chaîne qui ne contient aucun caractère se note "" et elle est une chaîne correcte d’usage assez
fréquent.
On ne peut utiliser ni les opérateurs d’égalité == et !=, ni les opérateurs d’ordre <, >, <=, >= sur les
chaînes de caractères. Nous verrons beaucoup plus tard dans ce cours comment comparer des chaînes
de caractères.
Le type String comporte un opérateur utile : la concaténation de chaîne notée + qui permet de
coller bout à bout deux chaînes de caractères : "ab"+"cd" donne la chaîne "abcd".

2.3 Les différentes sortes d’expression


En Java, une expression est un moyen de calculer une valeur d’un type donné. Une expression a
donc un type, celui du résultat qu’elle calcule.
Il y a quatre sortes d’expressions :
— une valeur est une expression. Le résultat du calcul de cette expression est la valeur elle-même.
Par exemple 17 est une expression qui permet de calculer 17. Idem pour true, ’a’, "toto".
— une variable est une expression. Le calcul consiste à aller chercher la valeur stockée dans la
variable, à lire cette valeur dans la mémoire. Dans notre premier exemple, euros est une
expression qui désigne une valeur entrée au clavier.

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

2.3.1 Priorité des opérateurs


Lorsqu’il y a plusieurs opérateurs dans une expression sans parenthèses, il y a une priorité qui
définit comment se calcule l’expression. Par exemple 10+3*2 sera calculée comme 10+(3*2). On dit
que la multiplication est prioritaire par rapport à l’addition.
La priorité des opérateurs est la suivante (du plus prioritaire au moins prioritaire) :
— le signe - unaire qui calcule l’opposé d’un nombre. Par exemple -(x*3).
— la multiplication, la division et le modulo (reste de la division)
— l’addition et la soustraction.
Si on ne se rappelle pas de la priorité des opérateurs, on peut toujours utiliser des parenthèses pour
préciser le calcul que l’on souhaite écrire.

2.4 Eléments de conception des programmes


En début de cours, nous allons faire des programmes très simples qui ne nécessitent pas une longue
analyse. Nous allons néanmoins prendre l’habitude de procéder à trois choses avant de commencer à
écrire les instructions.
1. Exprimer le but du programme
2. Déterminer quelles données sont nécessaires au programme et choisir lesquelles seront des
entrées et lesquelles seront dans le programme.
3. Déterminer quel sera le résultat
4. Ecrire le programme en Java
Reprenons point par point les différentes étapes.

2.4.1 Exprimer le but du programme


S’il s’agit d’un exercice, le but du programme est présent dans l’énoncé. Eventuellement, il faut
reprendre cet énoncé et le clarifier. S’il y a plusieurs interprétations possibles, il faut en choisir une. Il
est donc possible de reformuler l’énoncé pour le simplifier ou le compléter.
S’il n’y a pas d’énoncé préalable, il faut écrire l’équivalent d’un énoncé d’exercice, qui précise ce
que doit faire le programme.

2.4.2 Travail sur les données


Il faut identifier quelles sont toutes les données nécessaires pour traiter le problème et pour chaque
donnée, déterminer un type Java. Parmi les données, il faut déterminer lesquelles vont changer lors
des différentes exécutions du programme. Ce seront les entrées saisies au clavier. Les données qui
seront les mêmes pour toutes les exécutions du programme pouront être inscrites dans le programme.
Dans l’exemple de notre premier programme, les données sont la somme en euro et le taux de
change. Nous avons choisi dans ce programme de considérer la somme comme une entrée et le taux
de change comme une donnée inscrite dans le programme. Ce n’est probablement pas un bon choix
puisque le taux de change varie constamment. Le travail sur les données n’a donc pas été très perti-
nent 1 .
1. En fait, ce choix a été fait pour simplifier le premier exemple.

6 c
NFA031 CNAM 2016
CHAPITRE 2. EXPRESSIONS ET ALGORITHMES
2.4. ELÉMENTS DE CONCEPTION DES PROGRAMMES

2.4.3 Travail sur le résultat


Il s’agit de préciser combien il y a de valeurs dans le résultat et quels sont leurs types Java.
A l’issue du travail sur les données et le résultat, on peut tracer un petit graphique qui illustre les
entrées et sorties du programme avec pour chacune un nom et un type Java.
e1 e2
int int

Math.min

min
int

2.4.4 Ecriture du programme Java


Cette étape devra être raffinée ultérieurement, pour réaliser des programmes complexes. Pour
les petits programmes du début d’année, il est possible de commencer à les écrire directement dans
l’éditeur ou l’environnement de programmation.
Les deux premières lignes seront un copier-coller des lignes suivantes avec seulement le nom de
programme à changer :

public class Conversion {


public static void main (String[] args) {

Ensuite viennent les instructions. La structure de nos premiers programme sera :


1. saisir les entrées au clavier
2. calculer le résultat
3. afficher le résultat
Assez rapidement, il pourra y avoir des variantes à cette structure, avec notamment des entrelace-
ment d’entrées et de sorties.

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 :

public class PrixTTC {


public static void main (String[] args) {

double pHT,pTTC;
int t;
Terminal.ecrireString("Entrer le prix HT: ");
pHT = Terminal.lireDouble();

Terminal.ecrireString("Entrer taux (normal->0 reduit ->1) ");


t = Terminal.lireInt();
if (t==0){
pTTC=pHT + (pHT*0.2);
}else {
pTTC=pHT + (pHT*0.055);
}
Terminal.ecrireStringln("La somme TTC: "+ pTTC );
}
}

Ce programme est constitué d’une suite de 8 instructions. Il s’exécutera en exécutant séquentiel-


lement chacune de ces instructions :
1. déclaration de pHT et pTTC
2. déclaration de t
3. affichage du message "Entrer le prix HT:"
4. la variable pHT reçoit la valeur entrée au clavier.
5. affichage du message "Entrer taux (normal->0 reduit ->1)"
6. la variable t reçoit la valeur entrée au clavier (0 ou 1)

2 c
NFA031 CNAM 2016
CHAPITRE 3. CONDITIONNELLE ET BOUCLES 3.3. ITÉRATION

7. Java reconnaît le mot clé if et fait donc les choses suivantes :


(a) il calcule l’expression qui est entre les parenthèses t==0. Le résultat de t==0 dépend de
ce qu’a entré l’utilisateur. S’il a entré 0 le résultat sera true, sinon il sera false.
(b) Si le résultat est true, les instructions entre les accolades sont exécutées. Ici, il n’y en
a qu’une :pTTC=pHT + (pHT*0.2); qui a pour effet de donner a pTTC le prix TTC
avec taux normal. Si le résultat est false, ce sont les instructions dans les accolades
figurant après le else :pTTC=pHT + (PHT*0.055);
8. la valeur de pTTC est affichée. Cette dernière instruction est toujours exécutée : elle n’est pas
à l’intérieur des accolades du if ou du else. La conditionnelle a servi à mettre une valeur
différente dans la variable pTTC, mais il faut dans les deux cas afficher cette valeur.

3.2.1 if sans else


Lorsqu’on veut dire : si condition est vraie alors faire ceci, sinon ne rien faire, on peut omettre
le else ainsi que les accolades qui suivent ce mot-clé.

3.2.2 tests à la suite


Lorsque le problème à résoudre nécessite de distinguer plus de 2 cas, on peut utiliser la forme
suivante :

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.

3.3.1 La boucle for


La boucle for permet de répéter une tâche un nombre de fois connus à l’avance. Ceci est suffisant
pour l’affichage de notre rectangle :
public class Rectangle {
public static void main (String[] args) {
for (int i=0;i<5;i=i+1){
Terminal.ecrireStringln("****");
}
}
}

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("****");
}
}
}

i va prendre les valeurs successives 0, 2, 4. Il y aura donc 3 répétitions. Ce programme affiche 3


lignes de 4 étoiles.
public class Rectangle {
public static void main (String[] args) {
for (int i=1;i<5;i=i+2){
Terminal.ecrireStringln("****");
}
}

4 c
NFA031 CNAM 2016
CHAPITRE 3. CONDITIONNELLE ET BOUCLES 3.3. ITÉRATION

i va prendre les valeurs successives 1, 3. Il y aura donc 2 répétitions. Ce programme affiche 2


lignes de 4 étoiles.
public class Rectangle {
public static void main (String[] args) {
for (int i=1;i<=5;i=i+2){
Terminal.ecrireStringln("****");
}
}
}

i va prendre les valeurs successives 1, 3, 5. Il y aura donc 3 répétitions. Ce programme affiche 3


lignes de 4 étoiles.
public class Rectangle {
public static void main (String[] args) {
for (int i=1;i==5;i=i+2){
Terminal.ecrireStringln("****");
}
}
}

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.

Les boucles sont nécessaires


Dans notre premier exemple, nous avons utilisé une boucle pour abréger notre code. Pour certains
problèmes, l’utilisation des boucles est absolument nécessaire. Essayons par exemple d’écrire un pro-
gramme qui affiche un rectangle d’étoiles dont la longueur est donnée par l’utilisateur (la largeur
restera 4).
Ceci ne peut se faire sans boucle car le nombre de fois où il faut appeler l’instruction d’affichage
dépend de la valeur donnée par l’utilisateur.
En revanche, cela s’écrit très bien avec une boucle for :
public class Rectangle2 {
public static void main (String[] args) {
int lignes;
Terminal.ecrireString("combien de lignes d’etoiles ?:");
lignes=Terminal.lireInt();
for (int i=0;i<lignes;i=i+1){
Terminal.ecrireStringln("****");
}
}
}

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 peut intervenir dans la boucle

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("****");
}
}
}

Ce programme affiche les lignes d’étoiles précédées du numéro de ligne.


C’est pour cela que l’on a parfois besoin que la valeur du compteur d’étapes ne soit pas son numéro
dans l’ordre des étapes. Par exemple, voici un programme qui affiche les n premiers entiers pairs avec
n demandé à l’utilisateur.
public class Combien {
public static void main (String[] args) {
int n;
Terminal.ecrireString("combien d’entiers pairs ?:");
n=Terminal.lireInt();
for (int i=0;i<n*2;i=i+2){
Terminal.ecrireInt(i);
Terminal.ecrireString(" , ");
}
}
}

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 + ",");
}
}
}

Pour en savoir plus

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

3.3.2 la boucle while


Certaines fois, la boucle for ne suffit pas. Ceci arrive lorsqu’au moment où on écrit la boucle, on
ne peut pas déterminer le nombre d’étapes.
Reprenons notre exemple de calcul de prix TTC. Dans cet exemple, nous demandions à l’utilisa-
teur d’entrer 0 pour que l’on calcule avec le taux normal et 1 pour le taux réduit.
Que se passe-t-il si l’utilisateur entre 4 par exemple ? 4 est diffèrent de 0 donc le else sera exécuté.
Autrement dit : toute autre réponse que 0 est interprétée comme 1.
Ceci n’est pas très satisfaisant. Nous voulons maintenant améliorer notre programme pour qu’il rede-
mande à l’utilisateur une réponse, tant que celle-ci n’est pas 0 ou 1.
Nous sentons bien qu’il faut une boucle, mais la boucle for est inadaptée : on ne peut dire a priori
combien il y aura d’étapes, cela dépend de la vivacité de l’utilisateur ! Pour cela, nous avons la boucle
while qui a la forme suivante :

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;
}
}

Il faudra, bien entendu, avoir déclaré testReponse avant la boucle.

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.

état de sortie de la boucle


Puisqu’on sort d’une boucle while la première fois que la condition est fausse, nous sommes
sûrs que dans notre exemple, en sortie de boucle, nous avons dans t une réponse correcte : 0 ou 1.
Les instructions qui suivent la boucle sont donc le calcul du prix TTC selon des 2 taux possibles.
Voici le code Java :
public class PrixTTC2 {
public static void main (String[] args) {

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 );
}
}

Terminaison des boucles while


On peut écrire avec les boucles while des programmes qui ne s’arrêtent jamais. C’est presque le
cas de notre exemple : si l’utilisateur n’entre jamais une bonne réponse, la boucle s’exécutera à l’in-
fini.
Dans ce cours, les seuls programmes qui ne terminent pas toujours et que vous aurez le droit d’écrire

8 c
NFA031 CNAM 2016
CHAPITRE 3. CONDITIONNELLE ET BOUCLES 3.3. ITÉRATION

seront de cette catégorie : ceux qui contrôlent les saisies utilisateurs.

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;
}
}
}

Au début de la première exécution du corps de boucle i vaut 1, Au début de la deuxième exécution


du corps de boucle i vaut 2, . . .. A chaque fois, on progresse vers le moment où i vaut 10. Ce cas est
atteint au bout de 10 étapes.
Attention, il ne suffit de se rapprocher du moment où la condition est fausse, il faut l’atteindre un
jour, ne pas la rater.
Le programme suivant, par exemple, ne termine jamais :
public class b3 {
public static void main (String[] args) {

int i=1;

while (i!=10){
Terminal.ecrireString(i + ",");
i=i+2;
}
}
}

Car i progresse de 1 à 3, puis à 5, à 7, à 9, à 11 et 10 n’est jamais atteint.

Quelle boucle choisir ?

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.

4.1 Construction du programme


La première chose à faire quand on est confronté à un nouveau problème est d’essayer de voir s’il
ne ressemblerait pas par hasard à un autre problème que nous avons déjà résolu. Dans ce cas, il ne
reste plus qu’à reproduire l’algorithme en l’adaptant.
Le plus difficile est quand nous sommes confrontés à un problème pour la première fois. De plus,
vous êtes en grande majorité en cours d’acquisition de la logique de programmation qui n’est pas
forcément naturelle. Il faut alors utiliser une méthode pour arriver à concevoir un nouveau programme.
La méthode présentée ci-dessous peut paraître longue et fastidieuse, et il est très tentant de brûler
les étapes... Foncer tête baissée dans votre éditeur de texte ou votre IDE préféré pour écrire un nouveau
programme tant que vous n’êtes pas suffisamment aguerri à la programmation, vous mènera bien
souvent à l’échec. Des heures à tourner en rond et à ne pas savoir par quel bout prendre le problème.
Appliquer la méthode avec sérieux et soin, vous mènera beaucoup plus certainement à une solution.
Après, au fil de votre acquisition d’expérience et au fur et à mesure que votre bibliothèque de solutions
s’enrichira, vous pourrez de plus en plus souvent coder directement en Java. Mais pour l’instant, c’est
hors de question à quelques rares exceptions près.

Étape 1 : Entrées et sorties


La première chose à faire consiste à déterminer les données nécessaires à la réalisation du calcul et
le ou les résultats attendus. Pour ce faire, il faut lire attentivement le sujet. A chaque donnée et chaque
résultat il faudra lui donner un nom et fixer le type de sa valeur.

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 :

Problème : Écrire un programme simulant un virus informatique. Ce programme demandera à


l’utilisateur un entier : le nombre de fois qu’il devra écrire un message à l’écran. Chacun de ces
messages sera pris au hasard dans une liste que vous établirez. Sur la même ligne que le message
et à sa suite, pour simuler la visualisation de l’avancement de l’opération en cours, le programme
affichera à l’écran un nouveau point toutes les demi-secondes entre 5 et 15 fois. Le nombre de fois
qu’un point affiché sera aussi pris au hasard. Ensuite, il affichera les texte "Fait" avant de passer
à la ligne suivante et afficher le message suivant.
Pour vous aider, tout d’abord, l’instruction suivante permet de tirer un nombre au hasard compris
entre p et n-1+p, bornes comprises, et de le mettre dans la variable alea supposée de type int :
alea = (int) (n *Math.random ()) + p;

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);

interrompra l’exécution du programme pendant 2 secondes.


Attention ! Pour pouvoir utiliser Thread.sleep, il faut ajouter une directive
throws InterruptedException
avant l’accolade sur la ligne de déclaration du programme principal :
public static void main(String[] args)
throws InterruptedException {

Pour la liste des messages, prévoyez en quelques-uns du genre "Reformatage du disque


dur" ou "Suppression de vos fichiers". Il ne vous restera plus qu’à afficher l’un d’entre-
eux en fonction d’un nombre tiré au hasard.

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.

2 NFA031 – CNAM 2017


CHAPITRE 4. COMMENT PROGRAMMER 4.1. CONSTRUCTION DU PROGRAMME

Étape 2 : Trouver une méthode de calcul ou comprendre celle proposée


Dans les exercices qui vous sont proposés, s’il ne s’agit ni d’un problème simple ni d’un problème
bien connu, une ou plusieurs indications sur la méthode de résolution vous sont indiquées. Si ce n’est
pas le cas, vérifiez bien que ce « nouveau » problème ne s’approche pas d’un autre déjà vu ou d’un
problème bien connu.
Lisez bien l’énoncé ! Une fois que vous vous êtes assuré de bien comprendre le problème (la
réponse à donner en fonction des entrées) et les indications, prenez un exemple de données et essayez
d’y appliquer à la main la méthode de résolution proposée. Refaites ceci avec de nouveaux exemples
jusqu’à ce que vous soyez apte à le faire « comme un automate », sans avoir ni à réfléchir ni à inventer.

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

nbMessages Ecran remarques


Recopie du virus dans tous les fichiers........Fait Le nombre de points
Destruction de la mémoire.............Fait est variable.
4
Effacement de tous les fichiers........Fait Il y a une pause de 500 ms
Destruction de la mémoire..........Fait entre chaque point
Envoi du virus à tous vos contacts............Fait
Recopie du virus dans tous les fichiers................Fait
Effacement de tous les fichiers.........Fait
6
Destruction de la mémoire................Fait
Recopie du virus dans tous les fichiers..........Fait
Vidage de la base de registres................Fait

TABLE 4.1 – jeu d’exemples

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.

NFA031 – CNAM 2017 3


4.1. CONSTRUCTION DU PROGRAMME CHAPITRE 4. COMMENT PROGRAMMER

Étape 3 : Cas particuliers


Il faut toujours envisager tous les cas y compris les cas erronés et ceux qui ne peuvent soit-disant
jamais arriver. Ce n’est pas toujours faisable suivant la complexité de la structure de données et du
programme, mais il faut au moins essayer d’en envisager un maximum.

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.

Étape 4 : Quelles instructions ?


Tant que vous n’avez pas une expérience suffisante et même si cette étape peut poser question,
c’est très probablement celle qui vous débloquera pour la suite de l’élaboration du programme. Elle
consiste à essayer de repérer quelles instructions structurées nous seront nécessaires.
Nous avons à notre disposition plusieurs types d’instructions. Si la déclaration de variables, l’af-
fectation et l’affichage de résultats à l’écran sont simples à mettre en œuvre, il n’en est pas de
même pour les instructions structurées : la conditionnelle (if ... else ...), la boucle tant que
(while...) et sa variante la boucle répéter...tant que (do...while...), et la boucle pour tout...
(for...). Revoyons à quoi servent ces instructions et voyons quand y penser.

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 :

4 NFA031 – CNAM 2017


CHAPITRE 4. COMMENT PROGRAMMER 4.1. CONSTRUCTION DU PROGRAMME

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.

NFA031 – CNAM 2017 5


4.1. CONSTRUCTION DU PROGRAMME CHAPITRE 4. COMMENT PROGRAMMER

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 :

alea = (int) (n *Math.random ()) + p;

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.

alea = (int) (6 *Math.random ());


si alea == 0 alors
écrire le premier message
sinon si alea == 1 alors
écrire le deuxième message
sinon si alea == 2 alors
écrire le troisième message
sinon si alea == 3 alors
écrire le quatrième message
sinon si alea == 4 alors
écrire le cinquième message
sinon
écrire le sixième message
fin si

En complément, il faudra bien sûr ne pas oublier de déclarer la variable alea.

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.

6 NFA031 – CNAM 2017


CHAPITRE 4. COMMENT PROGRAMMER 4.1. CONSTRUCTION DU PROGRAMME

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.

Choix des boucles


Bien entendu, si notre analyse précédente nous a amenés à conclure qu’il n’y aura pas de boucle
dans le programme, il ne sera pas nécessaire de faire ce choix.
Dans le cas où il y a une ou plusieurs boucles dans le programme en cours d’étude, pour chacune
d’elle, il faut déterminer quel type de boucle conviendra le mieux : un while ou un for ? Certes, il
est possible de se contenter d’un seul des deux types : toute boucle while peut être écrite à l’aide
d’une boucle for et, réciproquement, toute boucle for peut être écrite à partir d’une boucle while.
Mais, dans certains cas, la boucle while sera mieux adaptée alors que la boucle for le sera dans
d’autres.
On choisira de préférence :
— une boucle for quand on peut calculer le nombre de tours de boucle qu’il faudra faire au
moment d’y entrer ;

NFA031 – CNAM 2017 7


4.1. CONSTRUCTION DU PROGRAMME CHAPITRE 4. COMMENT PROGRAMMER

— 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.

Étape 5 : Agencement des instructions


Maintenant, il nous faut organiser les instructions que nous avons identifiées dans l’étape précé-
dente les unes par rapport aux autres. Bien entendu, s’il n’y en a pas plus d’une, il n’y a rien à faire de
plus. Nous pouvons passer à l’étape suivante.
Dans le cas de plusieurs instructions parmi la conditionnelle et les boucles, il faut travailler deux
instructions par deux instructions. Dans ce cas, nous avons 4 dispositions possibles :
— la première avant la seconde,
— la première après la seconde,
— la première dans la seconde et
— la première contenant la seconde.

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 :

8 NFA031 – CNAM 2017


CHAPITRE 4. COMMENT PROGRAMMER 4.1. CONSTRUCTION DU PROGRAMME

— 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

NFA031 – CNAM 2017 9


4.1. CONSTRUCTION DU PROGRAMME CHAPITRE 4. COMMENT PROGRAMMER

if de choix du message à afficher


for sur les points
affichage complémentaire
}

Étape 6 : Formalisation de l’algorithme


Maintenant que nous connaissons la structure du programme, il ne reste plus qu’à formaliser
l’algorithme. Pour ceux qui sont le plus à l’aise, il sera possible d’omettre cette étape pour coder
directement le programme en Java.
En fait, une fois que nous avons décidé quelles instructions seront nécessaires, puis que nous les
avons organisées, l’algorithme est pratiquement écrit comme vu dans l’exemple ci-dessus. On peut
affiner cet algorithme en essayant d’être plus précis. En faisant un bilan de ce que nous venons de
faire :
— nous avons déterminé lors de l’étape 1 les entrées et les sorties ;
— lors des étapes 2 et 3 nous avons construit un jeu de données envisageant, à la fois les cas
simples et les cas particuliers.
— les étapes suivantes nous ont amenés à un schéma de programme.
Il ne nous reste plus qu’à assembler tout ceci, déterminer les variables locales supplémentaires et à
affiner l’expression du calcul.

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

10 NFA031 – CNAM 2017


CHAPITRE 4. COMMENT PROGRAMMER 4.1. CONSTRUCTION DU PROGRAMME

Début
écrire "Nombre de messages : "
lire nbMessages

pour n allant de 1 à nbMessages


alea = un nombre entier pris au hasard entre 0 et 5
si alea == 0
écrire "Je reformate le disque dur."
sinon si alea == 1
écrire "Effacement de tous les fichiers."
sinon si alea == 2
écrire "Destruction de la mémoire."
sinon si alea == 3
écrire "Vidage de la base de registres."
sinon si alea == 4
écrire "Recopie du virus dans tous les fichiers."
sinon
écrire "Envoi du virus à tous vos contacts."
fin si

tempo = un nombre entier pris au hasard entre 0 et 5


pour k allant de 1 à tempo
écrire un point
temporiser 500 ms
fin pour
écrire "Fait"
fin pour
fin

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 !

Étape 7 : Écriture du code source


L’algorithme étant écrit, l’étape suivante consiste à le coder dans le langage de programmation
choisi. Comme tous les langages de programmation issus de l’impératif comme Java, C, C++, VBA,
etc. contiennent le même jeu d’instructions à la syntaxe près, si nous avons pris soin de ne pas intro-
duire d’instructions ou d’expressions spécifiques à un langage précis, celui-ci peut être traduit dans
n’importe quel langage évolué. Un point positif supplémentaire jouant en faveur de l’écriture d’un
algorithme avant le codage. Comme ce cours porte sur le langage Java, nous le traduirons dans ce
langage mais nous pourrions le traduire aussi simplement dans d’autres langages de programmation.
En général, si l’algorithme a été bien conçu, il ne s’agit que d’une simple transcription sans grosse
difficulté. Si vous avez à trop réfléchir dans cette étape, la cause est très probablement un manque
de précision dans l’algorithme - une idée exprimée de mnière trop floue bien souvent. Lors de la

NFA031 – CNAM 2017 11


4.1. CONSTRUCTION DU PROGRAMME CHAPITRE 4. COMMENT PROGRAMMER

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;

// n un entier : compteur du nombre de messages affichés


// *********sera déclaré dans la boucle

// tempo un entier : nombre de points à afficher


int tempo;

// k un entier : compteur du nombre de points affichés


// *********sera déclaré dans la boucle

// Début
// écrire "Nombre de messages : "
System.out.print("Nombre de messages : ");

// lire nbMessages
nbMessages = Terminal.lireInt();

// pour n allant de 1 à nbMessages


for (int n = 1; n <= nbMessages; n = n+1) {

// alea = un nombre entier pris au hasard entre 0 et 5


alea = (int) (Math.random() *6);

12 NFA031 – CNAM 2017


CHAPITRE 4. COMMENT PROGRAMMER 4.1. CONSTRUCTION DU PROGRAMME

// 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
}

// tempo = un nombre entier pris au hasard entre 0 et 5


tempo = (int) (Math.random() *11) + 5;

// pour k allant de 1 à tempo


for (int k = 1; k <= tempo; k++) {
// écrire un point
System.out.print(".");
// temporiser 500 ms
Thread.sleep(500);
// fin pour
}
// écrire "Fait"
System.out.println("Fait");
// fin pour
}
System.out.println("Fini ! Bonne réinstallation du système !");
// fin
}

NFA031 – CNAM 2017 13


4.2. EN RÉSUMÉ CHAPITRE 4. COMMENT PROGRAMMER

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.

4.3 Pièges classiques


Il est fréquent qu’un débutant en programmation confonde l’instruction conditionnelle (if) et la
boucle tant que (while). On trouve plusieurs cas de figure :
else à la suite d’un while
dans ce cas, l’erreur est vite repérée : vous obtiendrez un message d’erreur à la compilation
ou, sous un IDE comme Eclipse, le else sera souligné en rouge. Rappelez-vous que la boucle
while n’a pas de else. C’est le code qui suit le corps de la boucle qui est exécuté dès que
la condition de la boucle n’est plus vérifiée. Pour corriger ce problème il y a deux possibilités
en fonction de ce que l’on souhaite faire :

14 NFA031 – CNAM 2017


CHAPITRE 4. COMMENT PROGRAMMER 4.3. PIÈGES CLASSIQUES

— 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.

NFA031 – CNAM 2017 15


Chapitre 5

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

tab[0], tab[1], tab[2], tab[3], . . ., tab[6] } 7 Variables (cases)

Les tableaux sont des structures des données présentes dans tous les langages de programmation.

5.1 Spécificités des tableaux


Un tableau n’existe pas tant qu’on ne l’a pas créé. Les valeurs des types primitifs de Java (int,
double, boolean, char) existent sans qu’il soit nécessaire de les créer. Il n’est pas nécessaire de créer
la valeur 17 ou la valeur true. Pour les tableaux, c’est différent : il faut les créer pour qu’ils existent et
il y a un opérateur pour le faire : l’opérateur new.
Une tableau est une valeur d’un type. Ce type dépend de ce que l’on met dans le tableau. On a
ainsi le type des tableaux qui contiennent des nombres entiers ou tableaux d’int, le type des tableaux
qui contiennent des caractères char, etc. Pour chaque type qui existe en Java, on peut créer un tableau
qui contient plusieurs valeurs de ce type, chacune étant stockée dans une case du tableau.
Le type d’un tableau d’entier se note int[], celui d’un tableau de char se note char[]. Plus généra-
lement, si un type Java se note T, le type des tableaux contenant une valeur de ce type dans chaque
case se note T[].

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;

5.2 Déclaration et création


En Java, avant d’utiliser un tableau, il faut :
1. Déclarer une variable de type tableau (symbole []), en indiquant le type T de ses futures
cases ;

T [] tab; // tab est declare tableau de T


2. Créer explicitement la structure du tableau en mémoire (opération new), en donnant sa taille
et le type T de ses éléments. Cette taille ne pourra plus changer : en Java les tableaux sont de
taille fixe.
tab = new T[taille];

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.

2 NFA031 – CNAM 2017


CHAPITRE 5. TABLEAUX 5.2. DÉCLARATION ET CRÉATION

Exemples
:

int [] tabNum; // tabNum est un tableau avec cases de type int


double [] t; // t est un tableau avec cases de type double
String [] m; // m est un tableau avec cases de type String
tabNum[0] = 5; // provoque une erreur: le tableau n’existe pas

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];

réalise la création et l’initialisation d’un tableau de n cases de type T :


1. Allocation en mémoire d’un espace suffisant pour stocker n cases de type T.
2. Initialisation des cases du tableau avec des valeurs par défaut.
Les tableaux en Java sont de taille fixe. Une fois le tableau créé, l’espace qui lui est alloué en
mémoire ne peut pas changer. Par exemple, il est impossible d’ajouter ou d’enlever des cases d’un
tableau.

Exemple 1 : Déclaration, puis création du tableau tab avec trois entiers.


int [] tab; // Declaration
tab = new int[3]; // Creation
tab[0] = 7;

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 :

Exemple 2 : Déclaration et création en une seule instruction.


int [] tab = new int[3];

NFA031 – CNAM 2017 3


5.2. DÉCLARATION ET CRÉATION CHAPITRE 5. TABLEAUX

Initialisation par une liste de valeurs


Lorsqu’un tableau est de petite taille, il est possible de l’initialiser en donnant la liste des valeurs
de chaque case. On utilise la notation {v0 , v1 , . . . vn }, où vi est la valeur à donner à la case i du tableau.
Nous reprendrons souvent cette notation pour regrouper en une seule instruction la déclaration, la
création et l’initialisation d’un tableau.

Exemple 3 : Déclaration, création et initialisation d’un tableau en une seule instruction.


int [] tab = {1,9,2,4};

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

Valeurs par défaut à la création


Lors de la création, les cases d’un tableau sont initialisées avec des valeurs par défaut :
— les cases boolean sont initialisées à false.
— les cases numériques sont initialisées à 0.
— les cases char sont initialisées au caractère nul ’\0’.
— les cases référence 1 sont initialisées à la valeur null (référence nulle).

Exemple 4 : Valeurs par défaut dans les cases


int [] tb = new int[3];
char [] ch = new char[4];
boolean [] bt = new boolean[3];

L’opération new initialise les cases de ces tableaux par :

tb −→ 0 0 0

ch −→ ’\0’ ’\0’ ’\0’ ’\0’

bt −→ false false false

Longueur d’un tableau


La taille ou longueur d’un tableau est le nombre n des cases qu’il contient. Supposons que tab
désigne un tableau de taille n. On peut obtenir sa longueur par la notation tab.length. Les indices
du tableau tab sont alors compris entre 0 et tab.length-1. Cette notation sera souvent employée
pour fixer la valeur maximale qui peut prendre l’indice d’un tableau (voir exemple 6).
1. Voir le chapitre dédié aux objets.

4 NFA031 – CNAM 2017


CHAPITRE 5. TABLEAUX 5.3. ACCÈS AUX CASES

5.3 Accès aux cases


L’accès à une case de tableau permet de traiter cette case comme n’importe quelle variable indivi-
duelle : on peut modifier sa valeur dans le tableau, l’utiliser pour un calcul, un affichage, etc. L’accès
d’une case se fait via son indice ou position dans le tableau. En Java, la première position a pour indice
0, et la dernière, a l’indice n-1 (taille du tableau moins un).
premier indice → 0 i n-1 ← dernier indice (tab.length-1)
tab = 2.5
| {z }
tab.length=n

tab[i] vaut 2.5


L’accès aux cases de tab n’a de sens que pour les indices dans l’intervalle [0,. . ., tab.length-1].
Si i est un indice compris dans cet intervalle :
— tab[i] : est un accès à la case de position i dans tab. On peut consulter ou modifier cette
valeur dans le tableau. Exemple : tab[i] = tab[i] + 1;
— accès en dehors des bornes du tableau tab : si j n’est pas compris entre 0 et tab.length-1,
l’accès tab[j] provoque une erreur à l’exécution : l’indice j et la case associée n’existent
pas dans le tableau. Java lève l’exception ArrayIndexOutOfBoundsException.

Exemple 5 : Modification d’une case, accès en dehors des bornes d’un tableau.

public class Test {


public static void main (String args[]) {
double [] tab = {1.0, 2.5, 7.2, 0.6}; // Creation
// Affichage avant modification
Terminal.ecrireString("tab[0] avant = ");
Terminal.ecrireDoubleln(tab[0]);
tab[0] = tab[0] + 4;
// Affichage apr\‘es modification
Terminal.ecrireString("tab[0] apres = ");
Terminal.ecrireDoubleln(tab[0]);
// tab[5] = 17; // Erreur: indice en dehors des bornes
}
}

Ce programme affiche la valeur de la première case de tab, avant et après modification.


Java/Essais> Java Test
tab[0] avant = 1.0
tab[0] apres = 5.0
Si nous enlevons le commentaire de la ligne 11, l’exécution se termine par une erreur : l’indice 5
est en dehors des bornes du tableau.
Java/Essais> Java Test
tab[0] avant = 1.0
tab[0] apres = 5.0
Exception in thread "main" Java.lang.ArrayIndexOutOfBoundsException: 5
at Test.main(Test.Java:9)

NFA031 – CNAM 2017 5


5.3. ACCÈS AUX CASES CHAPITRE 5. TABLEAUX

5.3.1 Calcul du numéro de case


Le numéro de case dans une expression peut être le résultat du calcul d’une expression. Par
exemple, il est possible d’utiliser le contenu d’une variable de type int comme numéro de case.

Exemple 5 bis : affichage d’une case de tableau


Cet exemple utilise la méthode Math.random() qui renvoie un nombre tiré au sort entre 0 et 1.
public class LeNumero{
public static void main(String[] args){
double[] values = new double[3];
int lenumero;
values[0]=Math.random();
values[1]=Math.random();
values[2]=Math.random();
Terminal.ecrireString("Quel numéro de case? ");
lenumero = Terminal.lireInt();
Terminal.ecrireString("Contenu de la case: ");
Terminal.ecrireDoubleln(values[lenumero]);
}
}

Et voici deux exécutions de ce programme.

> Java LeNumero


Quel numéro de case? 2
Contenu de la case: 0.5789990719784663
> Java LeNumero
Quel numéro de case? 5
Contenu de la case: Exception in thread "main" Java.lang.ArrayIndexOutOfBoundsExce
at LeNumero.main(LeNumero.Java:11)

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;

5.3.2 Affinité entre tableaux et boucles for


Le numéro de case peut être cherché dans une variable. Il arrive souvent qu’on utilise une boucle
for pour effectuer une opération sur chacune des cases d’un tableau. On utilise alors la variable de la
boucle for pour désigner un numéro de case. A chaque tour de boucle, l’opération est effectuée sur
une des cases du tableau et la boucle est parcourue autant de fois qu’il y a de cases dans le tableau.

6 NFA031 – CNAM 2017


CHAPITRE 5. TABLEAUX 5.3. ACCÈS AUX CASES

Exemple 6 : Parcours pour affichage d’un tableau


La boucle fait varier l’indice du tableau tab entre i= 0 et i = tab.length-1.
Notons qu’on ne peut pas afficher un tableau directement avec System.out.print ni avec Termi-
nal.ecrireXXX : ces méthodes ne fonctionnent que pour les types int, double, boolean, char et String.
Pour afficher un tableau, il faut faire comme dans le programme qui suit un affichage case par case au
moyen d’une boucle for.
public class AfficheTab {
public static void main (String args[]) {
int[] tab = {10,20,30,40};
for (int i=0; i< tab.length; i++) {
Terminal.ecrireStringln(tab[i]);
}
}
}

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)

Exemple 7 : Boucles d’initialisation et d’affichage.


On souhaite initialiser un tableau avec des notes d’élèves saisies au clavier. Le programme de-
mande le nombre de notes à saisir, et crée un tableau lesNotes de cette taille. Une première boucle
initialise le tableau ; la boucle suivante affiche son contenu. Les itérations se font dans l’intervalle de
i=0 jusqu’à i <= lesNotes.length-1.

public class Notes {


public static void main (String args[]) {
int nombreNotes;
Terminal.ecrireString("Nombre de notes a saisir? ");
nombreNotes = Terminal.lireInt();
double [] lesNotes = new double[nombreNotes];
// Initialisation

NFA031 – CNAM 2017 7


5.4. EXEMPLES D’UTILISATION DE TABLEAUX CHAPITRE 5. TABLEAUX

for (int i=0; i<= lesNotes.length -1; i++) {


Terminal.ecrireString("Note no. " + (i+1) + "? ");
lesNotes[i] = Terminal.lireDouble();
}
// Affichage
Terminal.sautDeLigne();
Terminal.ecrireStringln("Notes dans le tableau:");
Terminal.ecrireStringln("**********************");
for (int i=0; i<= lesNotes.length -1; i++) {
Terminal.ecrireString("Note no. " + (i+1) + " = ");
Terminal.ecrireDoubleln(lesNotes[i]);
}
}
}

Java/Essais> Java Notes


Nombre de notes a saisir? 4
Note no. 1? 7.6
Note no. 2? 11
Note no. 3? 14
Note no. 4? 5

Notes dans le tableau:


**********************
Note no. 1 = 7.6
Note no. 2 = 11.0
Note no. 3 = 14.0
Note no. 4 = 5.0

5.4 Exemples d’utilisation de tableaux


Exemple 10 : Recherche du minimum et maximum dans un tableau d’entiers.
Il s’agit d’un exemple classique, correspondant à un type de problème courant.
Le cœur de l’algorithme consiste à conserver dans deux variables min et max le plus petit et le
plus grand élément qu’on a vu dans la partie du tableau déjà visitée. Un tour de boucle consiste à
regarder une case du tableau et mettre à jout les variables où cas où le contenu de la case serait plus
petit que le contenu de min ou plus grand que le contenu de max.
Pour initialiser le processus, on considère le contenu de la première case : c’est nécessairement
à la fois le plus petit et le plus grand vu jusqu’à ce point, vu que c’est le seul. Pour cette raison, on
initialise les deux variables min et max avec le premier élément du tableau. La boucle doit ensuite
parcourir une à une les autres cases. La comparaison se fait à partir du deuxième élément : c’est
pourquoi l’indice i débute à i=1.
1 public class minMax {
2 public static void main (String args[]) {
3 int n;
4 Terminal.ecrireString("Combien des nombres a saisir? ");

8 NFA031 – CNAM 2017


CHAPITRE 5. TABLEAUX 5.4. EXEMPLES D’UTILISATION DE TABLEAUX

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

Exemple 11 : Gestion de notes.


Nous modifions le programme de l’exemple 7 afin de calculer la moyenne des notes, la note
minimale et maximale, et le nombre de notes supérieures ou égales à 10. Nous reprenons la boucle de
recherche du minimum et maximum de l’exemple 10.
public class Notes {
public static void main (String args[]) {

NFA031 – CNAM 2017 9


5.4. EXEMPLES D’UTILISATION DE TABLEAUX CHAPITRE 5. TABLEAUX

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);
}}

Java/Essais> Java Notes


Nombre de notes a‘ saisir? 4
Note no. 1? 5
Note no. 2? 8
Note no. 3? 10
Note no. 4? 15
La moyenne des notes est: 9.5
Le nombre de notes >= 10 est: 2
La note minimum est: 5.0
La note maximum est: 15.0

Exemple 12 : Inversion d’un tableau de caractères.


La boucle d’inversion utilise deux variables d’itération i et j, initialisées avec le premier et le der-
nier élément du tableau. A chaque tour de boucle, les éléments dans i et j sont échangés, puis i est
incrémenté et j décrémenté. Il y a deux cas d’arrêt possibles selon la taille du tableau : s’il est de taille
impair, alors l’arrêt se produit lorsque i=j ; s’il est de taille pair, alors l’arrêt se fait lorsque j < i.
En conclusion, la boucle doit terminer si i >= j.

public class Inversion {


public static void main (String[] args) {

10 NFA031 – CNAM 2017


CHAPITRE 5. TABLEAUX 5.4. EXEMPLES D’UTILISATION DE TABLEAUX

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 :

NFA031 – CNAM 2017 11


5.5. VÉRIFICATION D’UNE PROPRIÉTÉ CHAPITRE 5. TABLEAUX

Java/Essais> Java Inversion


Combien de caracteres a saisir? 5
Un caractere: s
Un caractere: a
Un caractere: l
Un caractere: u
Un caractere: t
Le tableau saisit: salut
Le tableau inverse: tulas

5.5 Vérification d’une propriété


Lorsqu’on veut vérifier une propriété du tableau, c’est-à-dire lorsqu’on veut faire un programme
qui calcule un résultat booléen, il y a une assymétrie entre les deux réponses.
Généralement, ce type de propriétés peut se résumer par une question dont la réponse est oui ou
non. Voici des exemples de telles propriétés :
— Est-ce que tel élément appartient à tel tableau ?
— Est-ce que tous les nombres du tableau sont positifs ?
— Est-ce que tous les caractères d’un tableau de char sont des lettres ?
— Est-ce qu’il y a au moins une lettre parmi les éléments d’un tableau de char ?
Il arrive souvent que l’on ait à écrire un programme qui réponde à ce type de questions.
Développons l’exemple de la première question : est-ce que tel élément appartient à tel tableau ?
Pour répondre oui à la question, il peut suffire de regarder une case, la bonne, celle qui contient
l’élément en question. Pour répondre non, il est nécessaire d’avoir regardé toutes les cases et de
n’avoir trouvé l’élément dans aucune d’entre elles.
Pour répondre oui, il faut être sûr qu’il existe une case contenant l’élément, alors que pour ré-
pondre non, il faut que pour toute case, l’élément n’est pas dans cette case. Donc la réponse oui
(true) peut être déterminée dans le corps de la boucle, au moment où on ne regarde qu’une case, alors
que la réponse non (false) ne peut en aucun cas être déterminée à l’intérieur de la boucle. Ce n’est
qu’après la fin de la boucle que cette réponse peut être choisie.
Voyons une première version du programme qui parcourt toujours toutes les cases au moyen d’une
boucle for.

Exemple 13 : recherche d’un élément avec un for


On utilise une variable booléenne appartient qui contiendra false tant qu’on n’a pas trouvé
l’élément dans le tableau et true à partir du moment où on l’a trouvé. Cette variable est initialisée
à false parce qu’au début du programme, aucune case n’a été vue et le nombre n’a pas encore été
trouvé.
1 public class RechercheFor{
2 public static void main(String[] args){
3 int[] tab = {10,20,30,40};
4 int elem;
5 boolean appartient = false;
6 System.out.print("Entrez un nombre: ");
7 elem = Terminal.lireInt();
8 for (int numcase=0; numcase < 4; numcase = numcase+1){

12 NFA031 – CNAM 2017


CHAPITRE 5. TABLEAUX 5.5. VÉRIFICATION D’UNE PROPRIÉTÉ

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.

Exemple 14 : recherche d’un élément avec un while


On peut reprocher au programme qui parcourt toutes les cases du tableau de faire un travail inutile :
à partir du moment où l’élément a été vu dans une case, on peut arrêter de parcourir le tableau. On
peut alors préférer écrire une boucle while qui s’arrête lorsque l’élément est trouvé.
public class RechercheWhile{
public static void main(String[] args){
int[] tab = {10,20,30,40};
int elem;
boolean appartient = false;
System.out.print("Entrez un nombre: ");
elem = Terminal.lireInt();
int numcase=0;
while (numcase < 4 && appartient == false){
if (tab[numcase]==elem){
appartient = true;
}
numcase = numcase+1;
}
if (appartient){
System.out.println(elem + " appartient au tableau");
}else{
System.out.println(elem + " 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.

NFA031 – CNAM 2017 13


Chapitre 6

Exécution de programmes et mémoire (1)

6.1 Variables et mémoire


La mémoire contient les données du programme dans des variables. Une variable désigne un
emplacement en mémoire et elle a un nom. Ce qui fait varier la mémoire au cours de l’exécution d’un
programme sont trois choses :
— la déclaration d’une variable, qui se traduit par l’allocation d’un espace mémoire et son asso-
ciation avec le nom de la variable.
— l’affectation qui va stocker un valeur dans l’espace mémoire associé au nom de la variable.
— la fin de vie d’une variable qui fait que le nom ne désigne plus rien et l’espace mémoire
correspondant est libéré. Il pourra être réutilisé pour une autre variable.
Nous reviendrons sur le troisième point dans une section dédiée à la fin de la vie des variables et
nous concentrer sur les deux premiers points en prenant un exemple.

6.1.1 Exemple

1 public class Conversion {


2 public static void main (String[] args) {
3 double euros;
4 double dollars;
5 System.out.println("Somme en euros? ");
6 euros = Terminal.lireDouble();
7 dollars = euros *1.118;
8 System.out.println("La somme en dollars: ");
9 System.out.println(dollars);
10 }
11 }

L’exécution du programme commence par la déclaration de la variable euros. Cela conduit à


l’allocation d’un emplacement mémoire suffisamment grand pour y loger un nombre à virgule (8
octets en Java) qui sera à partir de là réservé à la variable euros. On dira que c’est la mémoire
privée de la variable.
Dans cette mémoire, il n’y a rien d’utilisable : le compilateur refusera toute utilisation du contenu
de cette mémoire tant qu’une valeur ne lui aura pas été affectée. Nous représenterons cela en notant
un point d’interrogation dans l’espace correspondant.

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 : ?

L’exécution de la ligne 5 ne change rien à la mémoire : c’est un affichage à l’écran. La ligne 6


affecte une valeur à la variable euros. Cela va se traduire par le stockage de cette valeur dans la
mémoire privée de la variable. Ce qui est stocké dans cette mémoire est une valeur lue au clavier. Pour
continuer à représenter une exécution du programme, nous supposons que la valeur 10.0 est lue au
clavier. Après exécution de la ligne 6, la mémoire est dans l’état suivant.

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.

2 NFA031 – CNAM 2017


CHAPITRE 6. EXÉCUTION ET MÉMOIRE 6.1. VARIABLES ET MÉMOIRE

6.1.2 Notion de mémoire privée


Nous avons introduit la notion de mémoire privée d’une variable. C’est un espace mémoire qui
est entièrement réservé à la variable et qui ne peut être modifié que par une affectation à cette même
variable. Le contenu de cet espace privé ne peut être consulté qu’en utilisant dans un calcul le nom de
cette variable.
Nous retrouverons cette notion d’espace privé pour d’autres choses que des variables : des ta-
bleaux et des méthodes. Il y a aussi une notion de mémoire privée du programme : lorsqu’on demande
l’exécution d’un programme Java, le système d’exploitation (Windows, Mac Os, Linux, Android,
Ios,. . .) va allouer des ressources à l’exécution ce ce programme et notamment un espace mémoire
privé où seul le programme java aura le droit de lire et d’écrire des informations. C’est cette mémoire
privée du programme que nous représentons dans nos dessins. A la fin de l’exécution du programme,
l’espace mémoire privé du programme est rendu au système d’exploitation qui pourra l’attribuer à un
autre programme pour son exécution.

6.1.3 Fin de vie d’une variable : les blocs


La vie d’une variable commence lors de l’exécution de sa déclaration. Sa vie se termine à la fin
de la suite d’instruction où elle est déclarée. Cette suite d’instruction s’appelle un bloc.
Il arrive souvent que l’on déclare des variables en début de la méthode main. Ces variables existent
jusqu’à la fin de l’exécution de la méthode main, qui est aussi la fin de l’exécution du programme.
Si une variable est déclarée dans la suite d’instruction du cas else d’un if, elle n’existe que jusqu’à
la fin de ce cas else.
Prenons un exemple.
1 public class Bloc{
2 public static void main(String[] args){
3 System.out.println("Bonjour");
4 if (Math.random()<0.5){
5 System.out.println("Je suis de bonne humeur");
6 }else{
7 String comment;
8 System.out.println("Comment allez-vous?");
9 comment=Terminal.lireString();
10 System.out.println("Vous allez " + comment);
11 }
12 System.out.println("Au revoir");
13 }
14 }

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

NFA031 – CNAM 2017 3


6.1. VARIABLES ET MÉMOIRE CHAPITRE 6. EXÉCUTION ET MÉMOIRE

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 :

1 public class Bloc2{


2 public static void main(String[] args){
3 for (int i=0; i<3; i=i+1){
4 System.out.println("i vaut "+i);
5 }
6 System.out.println("i vaut "+i);
7 }
8 }

A la compilation : erreur !

> javac Bloc2.java


Bloc2.java:6: error: cannot find symbol
System.out.println("i vaut "+i);
^
symbol: variable i
location: class Bloc2
1 error

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 :

1 public class Bloc3{


2 public static void main(String[] args){
3 int i;
4 for (i=0; i<3; i=i+1){
5 System.out.println("i vaut "+i);
6 }
7 System.out.println("i vaut "+i);
8 }
9 }

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.

4 NFA031 – CNAM 2017


CHAPITRE 6. EXÉCUTION ET MÉMOIRE 6.2. TRACE D’EXÉCUTION D’UN PROGRAMME

6.2 Trace d’exécution d’un programme


Un programme, lors de son exécution, utilise une mémoire pour stocker des données et interagit
avec son environnement via le clavier et l’écran. Comme il est long et fastidieux de dessiner la mé-
moire après l’exécution de chaque instruction, on peut utiliser une représentation synthétique sous
forme de tableau qui relate instruction par instruction l’exécution d’un programme.
Dans ce tableau, chaque ligne donne un état de la mémoire, du clavier et de l’écran à un moment
donné de l’exécution. Les colonnes représentent la mémoire, le clavier et l’écran. Plus précisément, la
première colonne contiendra le numéro de ligne de l’instruction exécutée, ensuite il y aura une colonne
pour chaque variable du programme, une colonne pour le clavier et une pour l’écran. On pourra encore
ajouter des colonnes, par exemple pour noter la valeur d’une condition d’un if ou d’une boucle.

6.2.1 Premier exemple de trace d’exécution

1 public class PrixTTC {


2 public static void main (String[] args) {
3 double pHT,pTTC;
4 int t;
5 Terminal.ecrireString("Entrer le prix HT: ");
6 pHT = Terminal.lireDouble();
7 Terminal.ecrireString("Entrer taux (normal->0 reduit ->1) ");
8 t = Terminal.lireInt();
9 if (t==0){
10 pTTC=pHT + (pHT*0.2);
11 }
12 else {
13 pTTC=pHT + (pHT*0.05);
14 }
15 Terminal.ecrireStringln("La somme TTC: "+ pTTC );
16 }
17 }

Après l’instruction Mémoire Entrées Sorties


pHt t pTTC clavier écran
NEP NEP NEP
3 ? NEP ?
4 ? ? ?
5 ? ? Entrer le prix HT:
6 10.0 ? 10.0
7 10.0 ? Entrer le taux(0,1):
8 10.0 1 1
9 10.0 1
13 10.0 1 10.5
15 10.0 1 10.5 La somme TTC: 10.5
Dans ce tableau, on note NEP (pour n’existe pas) dans les cases représentant des variables non
encore déclarées à ce moment de l’exécution. C’est notamment le cas sur la première ligne qui transcrit

NFA031 – CNAM 2017 5


6.2. TRACE D’EXÉCUTION D’UN PROGRAMME CHAPITRE 6. EXÉCUTION ET MÉMOIRE

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.

6.2.2 Trace d’exécution d’une boucle for

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 }

6 NFA031 – CNAM 2017


CHAPITRE 6. EXÉCUTION ET MÉMOIRE 6.3. LES TABLEAUX ET LA MÉMOIRE

Et voici le tableau qui retrace l’exécution de ce programme :


nb test total x i clavier écran
2 NEP NEP NEP
3 0 NEP NEP
4 0 ? NEP
5 0 ? NEP Entrez le multiplicateur :
6 0 3 NEP 3
7.init 0 3 1
7.test true 0 3 1
8 3 3 1
7.incr 3 3 2
7.test true 3 3 2
8 9 3 2
7.incr 9 3 3
7.test true 9 3 3
8 18 3 3
7.incr 18 3 4
7.test true 18 3 4
8 30 3 4
7.incr 30 3 5
7.test false 30 3 5
9 30 3 NEP
10 30 3 NEP La somme des 4 premiers multiples est :
11 30 3 NEP 30
12 30 3 NEP
La première ligne du tableau montre l’état initial avant exécution. L’instruction for a trois mor-
ceaux intervenant à des moments différents, c’est pourquoi on a distingué les effets de l’initialisation,
du test de la condition i<=4 (quand la condition vaut true, la boucle se poursuit, quand elle vaut
false, la boucle s’arrête), et de la modification de la variable i. Les autres types de boucle (while
et do ... while) ne comportent qu’un partie test.
La ligne 9 est prise en compte en ceci que l’accolade ferme un bloc, ce qui met fin à l’existence
de la variable i qui est locale à ce bloc. On aurait pu de même prendre en compte la ligne 13 qui met
fin aux variables total et x.
Un tel tableau relate une exécution donnée. Si on change la valeur x entrée ligne 6, il faut changer
tout le tableau. On voit sur cet exemple que le corps de la boucle, la ligne 8, est exécutée 4 fois au
cours du programme.

6.3 Les tableaux et la mémoire


Avec les tableaux arrive une nouvelle instruction qui a un effet sur la mémoire : l’instruction new.
Elle a pour but et pour effet d’allouer une mémoire privée pour le tableau. Cette mémoire n’est pas
identifiée par un nom mais par une adresse. Elle est dans une partie de la mémoire différente de celle
qui contient les variables.
La mémoire privée du programme est divisée en deux parties :
— la Pile (anglais : Stack) qui contient les mémoires privées des variables déclarées dans les
méthodes.

NFA031 – CNAM 2017 7


6.3. LES TABLEAUX ET LA MÉMOIRE CHAPITRE 6. EXÉCUTION ET MÉMOIRE

— 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.

6.3.1 Exemple de création d’un tableau

public class Memoire{


public static void main(String[] args){
boolean[] tab;
tab = new boolean[3];
tab[0] = true;
}
}

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 : ?

L’instruction new a deux effets :


— réserver un espace mémoire suffisament grand pour 3 booléens (au moins 3 bits) dans le tas
pour en faire la mémoire privée du tableau.
— renvoyer l’adresse de cet espace mémoire.
L’affectation de la ligne 4 met la valeur renvoyée par new, c’est-à-dire l’adresse de la mémoire
privée du tableau, dans la mémoire privée de la variable tab. Les variables de type tableau contiennent
l’adresse de la mémoire privée du tableau. La taille de l’adresse ne dépend pas de la taille du tableau et
c’est pourquoi l’on n’a pas besoin de spécifier la taille du tableau lors de la déclaration de la variable.
Après exécution de la ligne 4, la mémoire est dans l’état suivant.

Pile Tas

tab : adresse-1 boolean[]


adresse-1 0 false
1 false
2 false

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.

8 NFA031 – CNAM 2017


CHAPITRE 6. EXÉCUTION ET MÉMOIRE 6.3. LES TABLEAUX ET LA MÉMOIRE

Pile Tas

tab : adresse-1 boolean[]


adresse-1 0 true
1 false
2 false

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.

> java Memoire


[Z@15db9742

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.

6.3.2 Affectation entre tableaux


Lorsque dans un programme, on réalise une affectation entre variables de type tableau, cela reco-
pie l’adresse de la mémoire privée d’une variable dans la mémoire privée de l’autre. C’est la même
chose lorsque l’on fait une affectation entre deux variables de type int : l’entier stocké dans la mémoire
privée d’une variable est recopié dans la mémoire privée de l’autre variable.
Cette opération de copie d’adresse ne change rien dans le tas et ne crée pas de nouveau tableau.
Seule l’instruction new crée des tableaux.
Voyons un exemple.
1 public class Memoire2{
2 public static void main(String[] args){
3 boolean[] tab1, tab2;
4 tab1 = new boolean[3];
5 tab2 = tab1;
6 System.out.println(tab1);
7 System.out.println(tab2);
8 tab1[0] = true;
9 System.out.println(tab2[0]);
10 }
11 }

Après exécution des lignes 3 et 4, la mémoire est dans l’état suivant :

Pile Tas

tab1 : adresse-1 boolean[]


tab2 : ? adresse-1 0 false
1 false
2 false

NFA031 – CNAM 2017 9


6.3. LES TABLEAUX ET LA MÉMOIRE CHAPITRE 6. EXÉCUTION ET MÉMOIRE

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

tab1 : adresse-1 boolean[]


tab2 : adresse-1 adresse-1 0 false
1 false
2 false

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.

> java Memoire2


[Z@15db9742
[Z@15db9742
true

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

tab1 : adresse-1 boolean[]


tab2 : adresse-1 adresse-1 0 true
1 false
2 false

A la ligne 9, c’est donc true qui est affiché.

6.3.3 Comparaisons d’adresses


Les opérateurs == et != peuvent comparer le contenu de deux variables de type tableau. Ce sont
les adresses qui sont comparés et non pas le contenu des cases de tableaux.
Prenons un exemple avec deux tableaux aux contenus identiques.
public class CompTab{
public static void main(String[] args){
int[] t1 = {10, 20};
int[] t2 = {10, 20};
if (t1 == t2){
System.out.println("t1 et t2 sont égaux");

10 NFA031 – CNAM 2017


CHAPITRE 6. EXÉCUTION ET MÉMOIRE 6.3. LES TABLEAUX ET LA MÉMOIRE

}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.

> java CompTab


t1 et t2 ne sont pas égaux
t1 et t2 sont différents

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){

NFA031 – CNAM 2017 11


6.3. LES TABLEAUX ET LA MÉMOIRE CHAPITRE 6. EXÉCUTION ET MÉMOIRE

System.out.println("tab1 et tab2 sont égaux");


}else{
System.out.println("tab1 et tab2 ne sont pas égaux");
}
}
}

L’exécution produit l’affichage suivant :


> java Memoire2Bis
[Z@15db9742
[Z@15db9742
true
tab1 et tab2 sont égaux
Une autre façon d’expliquer le comportement de l’opérateur == est de dire qu’il teste si tab1 et
tab2 sont deux moyens différents d’accéder au même tableau dans le tas.

6.3.4 Évolutions de l’état de la mémoire


Il y a trois opérations qui modifient l’état de la mémoire :
— L’allocation de mémoire privée à un élément du programme.
— L’écriture dans une mémoire privée d’un élément du programme.
— La désallocation d’une mémoire privée.
Pour ce qui est de la pile, voyons quelles instructions apportent les modifications.
— L’allocation de mémoire privée est l’effet de l’exécution d’une déclaration de variable.
— L’écriture dans une mémoire privée d’une variable est l’effet d’une affectation dans cette va-
riable. Le nom de la variable est à gauche de l’affectation, à droite, il y a le calcul qui détermine
ce qui est écrit dans cette mémoire privée.
— La désallocation d’une mémoire privée d’une variable a lieu lorsque la variable cesse d’exister,
à la fin de l’exécution du bloc d’instructions où elle est déclarée.
Voyons maintenant ce qui modifie l’état du tas.
— L’allocation de mémoire privée est l’effet de l’exécution d’une instruction new. Il s’agit de la
mémoire privée d’un tableau. L’initialisation d’une variable avec un tableau écrit en accolade
a le même effet. C’est un new déguisé.
— L’écriture dans une mémoire privée d’un tableau est l’effet de l’affectation d’une valeur à une
case de ce tableau. C’est donc la même opération que pour la pile, mais la différence est qu’à
gauche du signe =, il n’y a pas que le nom d’une variable mais aussi des crochets et un numéro
de case (directement ou via un calcul). Pour savoir si une affectation écrit dans la pile ou dans
le tas, il suffit de regarder s’il y a des crochets ou pas àgauche du =.
— La désallocation d’une mémoire privée d’un tableau a lieu lorsque ce tableau n’est plus ac-
cessible depuis le programme (il n’y a aucun moyen d’accès, plus de variable qui contienne
l’adresse de ce tableau) et que par ailleurs, le programme a besoin de mémoire pour créer de
nouveaux tableaux. En pratique, on ne sait jamais bien quand a lieu la désallocation et on peut
tout simplement ne pas s’en préoccupper et supposer que cette opération n’a jamais lieu.
Voyons un exemple de programme contenant un tableau qui n’est plus accessible.
1 public class Memoire3{
2 public static void main(String[] args){

12 NFA031 – CNAM 2017


CHAPITRE 6. EXÉCUTION ET MÉMOIRE 6.4. LES STRING ET LA MÉMOIRE

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 }

Après exécution de la ligne 5, l’état de la mémoire est le suivant.

Pile Tas

tab : adresse-1 double[]


adresse-1 0 0.5
1 0.0
2 0.0

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

tab : adresse-2 double[]


adresse-1 0 0.5
1 0.0
1 0.0
double[]
adresse-2 0 0.0
1 1.6
2 0.0

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.

6.4 Les String et la mémoire


Les String comme les tableaux sont stockés dans le tas et les variable de type String contiennent
l’adresse de la mémoire privée d’une chaîne dans la mémoire.
1 public class Memoire4{
2 public static void main(String[] args){
3 String msg1 = "Bonjour";
4 String msg2 = "Au revoir";

NFA031 – CNAM 2017 13


6.4. LES STRING ET LA MÉMOIRE CHAPITRE 6. EXÉCUTION ET MÉMOIRE

5 String msg3 = msg1;


6 }
7 }

Après exécution de la ligne 4, l’état de la mémoire est le suivant.


Pile Tas

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 }

14 NFA031 – CNAM 2017


CHAPITRE 6. EXÉCUTION ET 6.5.
MÉMOIRE
REPRÉSENTATION DES ADRESSES PAR DES FLÈCHES

L’exécution du programme donne le résultat suivant :


> java Memoire5
<Bonjour2>
<Bonjour2>
chaîne différentes selon ==
L’état de la mémoire à partir de la fin de l’exécution de la ligne 5 est le suivant.

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.

6.5 Représentation des adresses par des flèches


Lorsqu’on représente un état de la mémoire, l’utilisation des adresses n’est pas toujours très par-
lante. On préfère souvent représenter les références par des flèches. Ce qui compte, dans une flèche,
c’est son point d’arrivée.
Prenons un exemple d’état mémoire vu précédemment.

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

NFA031 – CNAM 2017 15


6.6. RÉSUMÉ DU CHAPITRE CHAPITRE 6. EXÉCUTION ET MÉMOIRE

6.6 Résumé du chapitre


— La mémoire est divisée en deux parties : la pile et le tas
— La pile contient les mémoires privées des variables
— Le tas contient les mémoires privées des tableaux et des chaînes de caractères.
— Les mémoires privées des variables de type tableau ou de type String contiennent une adresse
désignant un espace dans le tas où est enregistrée le contenu du tableau ou de la chaîne.
— Pour les tableaux et les chaînes, == et != comparent les adresses et non le contenu des tableaux
et des chaînes.

16 NFA031 – CNAM 2017


Chapitre 7

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.

7.1 Rappel de ce que l’on a déjà vu


Nous utilisons des méthodes prédéfinies depuis le début de ce cours. Nous connaissons donc ce
qu’est un appel de méthode.
Les exemples de méthodes que nous utilisons couramment sont :
— les méthodes de sortie à l’écran : System.out.println
— les méthodes d’entrée au clavier : Terminal.lireInt
— les méthodes mathématiques : Math.min, Math.random

7.1.1 Signature d’une méthode


Pour appeler une de ces méthodes, il faut écrire son nom suivi d’une liste de paramètres entre
parenthèses. Pour utiliser une méthode, il faut connaitre le nombre et le type de ses paramètres. Il
faut également savoir si elle renvoie un résultat et le cas échéant, le type de son résultat. On appelle
signature d’une méthode la liste des types de paramètres et du résultat éventuel de la méthode.
Les quatre méthodes mentionnées ci-dessus ont les signatures suivantes, notées graphiquement.
On peut également décrire les signatures textuellement. On utilise alors le petit mot-clé void pour
dire qu’une méthode ne renvoie pas de résultat.
System.out.println : String → void
Terminal.lireInt : → int
Math.min : int, int → int
Math.random : → double

7.1.2 Instruction ou expression


Les méthodes ne s’utilisent pas de la même façon selon qu’elles renvoient un résultat ou pas. Si
elles ne renvoient pas de résultat, on les considère comme des instructions. Les appels à ces méthodes
apparaissent généralement seuls sur une ligne de programme. On les appelle parfois des procédures.
System.out.println("Bonjour les amis");

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

F IGURE 7.1 – Signatures de quelques méthodes

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));

7.1.3 Paramètres dans un appel de méthode

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

2 NFA031 – CNAM 2017


CHAPITRE 7. MÉTHODES 7.2. POURQUOI ET COMMENT ÉCRIRE UNE MÉTHODE

7.2 Pourquoi et comment écrire une méthode


7.2.1 Pourquoi écrire des méthodes ?
Jusqu’à présent, nous avons écrit des programmes qui ne comportaient qu’une méthode : la mé-
thode main. Il est souvent intéressant d’écrire plusieurs méthodes et ce pour plusieurs raisons :
— Pour diviser un gros programme en petits éléments plus facile à écrire, à comprendre et à tester
qu’un grand main, avec beaucoup de boucles et de if imbriqués les uns dans les autres.
— Pour éviter de réécrire les mêmes suites d’instructions plusieurs fois dans le programme. Par
exemple, s’il y a plusieurs tableaux qu’on souhaite afficher dans un programme, il y aura autant
de boucles for à écrire que de tableaux à afficher et leur corps seront quasiment identiques,
seul le nom du tableau étant différent à chaque fois. On pourra avantageusement remplacer les
boucles par des appels à une méthode unique d’affichage de tableaux d’un certain type.
Diviser un code en plusieurs morceaux, chacun correspondant à une méthode, ne doit pas être
fait au hasard : chaque méthode doit avoir une cohérence interne, c’est-à-dire réaliser un travail bien
précis que l’on peut expliquer.

7.2.2 Étude de la méthode main


Pour savoir comment écrire une méthode, on peut commencer par regarder la méthode que nous
savons déjà écrire, à savoir la méthode main. Prenons un exemple de programme.
1 public class Conversion {
2 public static void main (String[] args) {
3 double euros;
4 double dollars;
5 System.out.println("Somme en euros? ");
6 euros = Terminal.lireDouble();
7 dollars = euros *1.118;
8 System.out.println("La somme en dollars: ");
9 System.out.println(dollars);
10 }
11 }
La méthode commence par un entête qui figure sur la ligne numéro 2, suivi d’une liste d’instruc-
tions entre accolades (accolade ouvrante en fin de ligne 2, fermante en ligne 10, instructions des lignes
3 à 9).
Voyons les différentes composantes de l’entête :
— public static : deux mot-clés de Java que nous n’expliquerons pas pour le moment et
qui seront toujours écrits en début d’entête pour toutes les méthodes.
— void : spécifie que la méthode ne renvoie pas de résultat. Si au contraire, la méthode renvoyait
un résultat, ce serait le type de ce résultat qui apparaîtrait à cette position de l’entête.
— main : le nom de la méthode. Dans le cas de la méthode main, ce nom est imposé. C’est
cette méthode qui est toujours exécutée lorsqu’on demande à exécuter un programme. Pour les
autres méthodes, leur nom sera au libre choix du programmeur, comme le nom des variables
du programme.
— (String[] args) : c’est la liste des paramètres de la méthode. Ici, il n’y a qu’un para-
mètre. Ce paramètre a pour nom args et pour type String[]. Ce paramètre est comme une
sorte de variable, utilisable dans le programme.

NFA031 – CNAM 2017 3


7.3. MÉTHODES SANS RÉSULTAT (VOID) CHAPITRE 7. MÉTHODES

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]);
}
}
}

Et deux exécutions de ce programme :

> java ArgMain


Taille de args: 0
> java ArgMain un deux 47 789
Taille de args: 4
Contenu de args[0]: un
Contenu de args[1]: deux
Contenu de args[2]: 47
Contenu de args[3]: 789

7.3 Méthodes sans résultat (void)


Une méthode sans résultat ou procédure est un bout de code qui a un nom et qui peut être appelé
depuis le reste du programme.
En Java, une méthode est forcément déclarée dans une classe. Dans sa forme la plus simple, elle
s’exprime ainsi :
public static void nomDeLaMéthode() {
Corps de la méthode...
}

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();

4 NFA031 – CNAM 2017


CHAPITRE 7. MÉTHODES 7.3. MÉTHODES SANS RÉSULTAT (VOID)

System.out.println();
dessinerLigne();
}

public static void dessinerLigne() {


for (int i= 0; i < 60; i++) {
System.out.print(’*’);
}
System.out.println();
}
}

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...

public static void dessinerDeuxLignes() {


dessinerLigne();
dessinerLigne();
}
}

Cette méthode appelle la méthode dessinerLigne déjà écrite.

7.3.1 Variables locales


Les variables déclarées dans une méthode sont locales à cette méthode : elles ne peuvent être
utilisées que par les instructions de la méthode.
De plus, comme les méthodes sont un moyen pour découper le code en unités plus ou moins
indépendantes, que l’on souhaite pouvoir modifier et comprendre indépendamment les unes des autres,
la question des variables locales y est d’une grande importance.

NFA031 – CNAM 2017 5


7.3. MÉTHODES SANS RÉSULTAT (VOID) CHAPITRE 7. MÉTHODES

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 {

public static void dessinerLigne() {


int a= 0;
while (a < 60) {
Terminal.ecrireChar(’*’);
a= a+1;
}
Terminal.sautDeLigne();
}

public static void main(String[] args) {


int a= 20;
dessinerLigne();
Terminal.ecrireIntln(a);
}
}

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.

6 NFA031 – CNAM 2017


CHAPITRE 7. MÉTHODES 7.3. MÉTHODES SANS RÉSULTAT (VOID)

7.3.2 Paramètres d’une méthode


Le système que nous venons de décrire est un peu trop rigide. On désire très souvent fournir des
informations à la méthode pour qu’elle adapte son comportement en conséquence. Dans notre cas,
on pourrait par exemple vouloir choisir la longueur de la ligne au moment de l’appel (alors qu’il est
actuellement fixé une fois pour toutes à 60).
Pour cela, on va utiliser des paramètres. L’idée est de pouvoir appeler la méthode en écrivant

dessinerLigne(30);

pour dessiner une ligne de 30 ’*’.


30 est ici un paramètre, c’est-à-dire une information passée à la méthode lors de son appel.
C’est le même mécanisme qui est mis en ¡uvre quand nous ap-
pelons Terminal.ecrireStringln("bonjour tout le monde"),
Terminal.ecrireIntln(x*2) ou Terminal.ecrireDouble(z). Dans tous ces
cas, "bonjour tout le monde", x*2 et z sont des valeurs passées en paramètre aux méthodes
ecrireStringln, ecrireIntln et ecrireDouble.

Déclaration d’une méthode avec paramètres

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();
}

... suite de la classe...

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);
}
}

NFA031 – CNAM 2017 7


7.3. MÉTHODES SANS RÉSULTAT (VOID) CHAPITRE 7. MÉTHODES

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();
}

... suite de la classe...

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, ’-’);

Localité des paramètres


Le passage de paramètre en Java se fait par valeur. Le paramètre est une sorte de variable créée
lors de l’appel de la méthode, qui reçoit la valeur qui est calculée pour ce paramètre lors de l’appel.
Une modification d’un paramètre ne modifie pas la variable correspondante du programme prin-
cipal.
Ainsi, si nous considérons le programme suivant :
public class Localite {
public static void augmenterMalEcrit(int val) {
val= val + 1;
}

8 NFA031 – CNAM 2017


CHAPITRE 7. MÉTHODES 7.3. MÉTHODES SANS RÉSULTAT (VOID)

public static void main(String[] args) {


int k= 10;
augmenterMalEcrit(k);
Terminal.ecrireIntln(k);
}
}

Lors de l’appel de augmenterMalEcrit, ligne 9, on crée une variable val, locale à


augmenterMalEcrit. la valeur de k va être copié dans val.
Nous avons donc deux variables distinctes : k et val, qui contiennent toutes les deux la valeur
10. La ligne 3 augmente la valeur de val, qui vaut 11, mais absolument pas la valeur de k, qui reste
inchangé. Quand augmenterMalEcrit se termine, on revient dans main. val disparaît et on
affiche la valeur de k, soit 10.
Supposons maintentant que nous définissions une variable val dans le main :
public class Localite2 {
public static void augmenterMalEcrit(int val) {
val= val + 1;
Terminal.ecrireStringln("La valeur du val de augmenterMalEcrit : "+ val);
}

public static void main(String[] args) {


int k= 10;
int val= 30;
augmenterMalEcrit(k);
Terminal.ecrireIntln(k);
Terminal.ecrireIntln(val);
}
}

Cette variable est complètement distincte du val de augmenterMalEcrit. Le déroulement


du programme sera le même que précédemment, et affichera :

La valeur du val de augmenterMalEcrit : 11


10
30

7.3.3 Appel d’une méthode définie dans une autre classe


Jusqu’ici, quand nous avons voulu réutiliser une méthode dans un nouveau programme, nous
l’avons tout bonnement recopiée. Mais, en pratique, on préfère ne pas dupliquer le code, fût-ce au
moyen d’un copier/coller. L’idéal est donc de regrouper
On tente donc de créer des bibliothèques de méthodes. Vous en utilisez déjà une : la classe
Terminal. On pourrait vouloir utiliser notre LigneMania de la même manière. C’est en fait très
facile. Dans l’état actuel de vos connaissances en Java, pour utiliser les méthodes d’une classe A
depuis une classe B :
— il faut que le code des deux classes soit dans le même dossier ;

NFA031 – CNAM 2017 9


7.4. MÉTHODES CALCULANT UN RÉSULTAT CHAPITRE 7. MÉTHODES

— on peut appeler les méthodes de A en préfixant simplement le nom de la méthode par A.


Exemple :
public class TestLignes {
public static void main(String[] args) {
LigneMania.dessinerLigne(100,’-’);
}
}

7.4 Méthodes calculant un résultat


Une méthode calculant un résultat ou fonction ressemble à une méthode void, mais sa tâche est de
renvoyer une valeur, qui sera utilisée par la suite. L’exemple le plus immédiat, ce sont les fonctions
mathématiques. La fonction cos, de la classe Math, calcule ainsi le cosinus de son paramètre. On
l’utilise de la manière suivante :

double x= 3;
double y= Math.cos(x/2);

Vous utilisez par ailleurs plusieurs fonctions : Terminal.lireInt(),


Terminal.lireString(), etc... Elles ne prennent pas de paramètre, mais renvoient l’en-
tier, le texte... saisis par l’utilisateur.
La grande différence entre méthodes sans et avec résultat est donc que ces dernières renvoient
quelque chose. D’un point de vue pratique, dans une fonction :
— on met le type de la valeur renvoyée à la place du void des méthodes :
public static TYPE_RETOURNÉ NOM_FONCTION (PARAMÈTRES)

— on renvoie la valeur du résultat à l’aide de l’instruction return.


Commençons par un exemple : la méthode valeur absolue.
public class F1 {
public static double valeurAbsolue(double x) {
double resultat;
if (x < 0)
resultat= -x;
else
resultat= x;
return resultat;
}

public static void main(String[] args) {


double v= Terminal.lireDouble();
Terminal.ecrireDoubleln(valeurAbsolue(v));
}

10 NFA031 – CNAM 2017


CHAPITRE 7. MÉTHODES 7.4. MÉTHODES CALCULANT UN RÉSULTAT

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;
}
}

produira à la compilation le message d’erreur :


Err.Java:5: missing return statement
}
^

7.4.1 Méthodes qui retournent un tableau


Une méthode peut très bien renvoyer un tableau. Il suffit pour cela qu’elle le déclare comme type
de retour. La méthode suivante renvoie par exemple un tableau, dont la taille et le contenu des cases
sont donnés en paramètre.
public static double[] creerTableau(int taille, double valeur) {
double [] t= new double [taille]; // création du tableau
for (int i= 0; i < taille; i=i+1){
t[i]= valeur;
}
return t;
}

Remarquez en particulier la dernière ligne. On renvoie tout simplement t (sans crochet).


1. Sauf dans le cas où elle lève une exception, voir infra.

NFA031 – CNAM 2017 11


7.5. EXÉCUTION D’UN APPEL DE MÉTHODE CHAPITRE 7. MÉTHODES

7.5 Exécution d’un appel de méthode


Un appel de méthode est exécuté en plusieurs étapes faisant intervenir deux méthodes distinctes :
la méthode appelante dans laquelle se situe l’appel ; la méthode appelée qui doit être exécutée.
1. calcul de la valeur des paramètres. Ces valeurs sont obtenues en exécutant des expressions
situées dans la méthode appelante. Ce calcul donne une valeur pour chacun des paramètres.
2. allocation en mémoire d’un espace réservé à la méthode appelée, avec dedans les paramètres
et leurs valeurs calculées à l’étape précédente.
3. exécution des instructions du corps de la méthode appelée. Si dans ce corps, des variables
locales sont déclarées, elles sont ajoutées dans l’espace mémoire réservée à la méthode.
4. lorsque l’instruction à exécuter est un return, la valeur renvoyée est calculée.
5. l’exécution de la méthode est alors terminée. L’espace mémoire réservé est rendu libre, les
variables locales et paramètres sont détruits. la valeur calculée est renvoyée à la méthode
appelante.
Une fois la méthode appelée exécutée, la main repasse à la méthode appelante qui reprend l’exé-
cution de ses propres instructions, généralement en utilisant la valeur renvoyée.

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.

7.7 Erreurs fréquentes


— Un sous-programme ne demande jamais à l’utilisateur les valeurs de ses paramètres... celles-ci
sont connues au moment de l’appel. Le code suivant est illogique :
public static double carre(double x) {
x= Terminal.lireDouble(); // NON ! x est déjà connu
// et on le redemande ici sans raison
return x*x;
}

— Une méthode n’affiche pas son résultat ; elle le renvoie ;


— Les paramètres formels sont locaux à la méthode et sont donc indépendants des variables
utilisées dans la méthode appelante.

12 NFA031 – CNAM 2017


Chapitre 8

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.

8.2 Traiter les exceptions prédéfinies


Prenons l’exemple d’un tableau créé et rempli d’après des données saisies au clavier. Voyons
d’abord le programme nu, sans traitement des erreurs.
1 public class Excep_1{
2 public static void main(String[] args){

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 }

Quand tout va bien, le programme s’exécute jusqu’au bout.

> java Excep_1


Taille du tableau? 3
Entrez un nombre: 10
Entrez un nombre: 5
Entrez un nombre: 7
Quelle case voulez-vous doubler? 1
10 10 7

Mais un certain nombre d’erreurs peuvent se produire :


— Terminal.lireInt (lignes 6, 10 et 13) : TerminalException si autre chose qu’un entier est entré.
— new int[taille] (ligne 7) : NegativeArraySizeException si taille est négatif
— tab[indice] (ligne 14) : ArrayIndexOutOfBoundsException si indice n’est pas un numéro de
case correct.
Exemples de ces erreurs :

> java Excep_1


Taille du tableau? azerty
Exception in thread "main" TerminalException
at Terminal.exceptionHandler(Terminal.java:115)
at Terminal.lireInt(Terminal.java:25)
at Excep_1.main(Excep_1.java:6)
> java Excep_1
Taille du tableau? -10
Exception in thread "main" java.lang.NegativeArraySizeException
at Excep_1.main(Excep_1.java:7)
> java Excep_1
Taille du tableau? 3

2 NFA031 – CNAM 2017


CHAPITRE 8. EXCEPTIONS 8.2. TRAITER LES EXCEPTIONS PRÉDÉFINIES

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)

8.2.1 Résoudre les problèmes : l’instruction try...catch


L’instruction qui sert à attraper les exceptions a la structure suivante :

try{
instruction-1
instruction-2
...
instruction-n
}catch(NomDException e){
autre-instruction-1
autre-instruction-2
...
autre-instruction-n
}

Lors du déroulement normal du programme, les instructions instruction-1 à instruction-n


sont exécutées et les instructions situées après le catch (autre-instruction-1 . . .) ne sont pas
exécutées.
Si une des instructions instruction-1 à instruction-n lance l’exception NomDExcep-
tion, alors l’exécution de ces instructions-là s’arrête et ce sont les instructions autre-instruction-1
à autre-instruction-n qui sont exécutées.
Supposons que c’est l’instruction instruction-2 qui lance NomDException. Dans ce cas,
instruction-1 est exécutée, puis instruction-2 est en partie exécutée et ensuite autre-instruction-1
à autre-instruction-n sont exécutées. Dans ce cas, les instructions instruction-3 à
instruction-n ne sont pas exécutées.
Le lancement de l’exception fait que le déroulement normal du programme est interrompu et c’est
la suite des autres instructions (autre-instruction-1 à autre-instruction-n) qui est
exécutée.
Si une des instructions instruction-1 à instruction-n lance une exception qui n’est pas
NomDException, le programme s’arrête brutalement.
Le même try peut avoir plusieurs catch pour traiter des instructions différentes.

try{
liste-instuctions-1
}catch(Exception1 e){
liste-instructions-2
}catch(Exception2 e){
liste-instructions-3
}

NFA031 – CNAM 2017 3


8.2. TRAITER LES EXCEPTIONS PRÉDÉFINIES CHAPITRE 8. EXCEPTIONS

8.2.2 Traitement d’une exception de l’exemple


On peut commencer par le traitement de l’exception TerminalException pour notre programme.
Voici un premuier exemple d’utilisation du try...catch.
1 public class Excep_1_1{
2 public static void main(String[] args){
3 int[] tab;
4 int taille, indice;
5 try{
6 Terminal.ecrireString("Taille du tableau? ");
7 taille = Terminal.lireInt();
8 tab = new int[taille];
9 for (int i=0; i<taille; i=i+1){
10 Terminal.ecrireString("Entrez un nombre: ");
11 tab[i]=Terminal.lireInt();
12 }
13 Terminal.ecrireString("Quelle case voulez-vous doubler? ");
14 indice = Terminal.lireInt();
15 tab[indice]=tab[indice]*2;
16 for (int i=0; i<taille; i=i+1){
17 Terminal.ecrireString(tab[i]+" ");
18 }
19 Terminal.sautDeLigne();
20 }catch(TerminalException e){
21 Terminal.ecrireString("On vous demandait un nombre entier, ");
22 Terminal.ecrireStringln("Vous avez tapé autre chose.");
23 Terminal.ecrireStringln(" Tant pis pour vous.");
24 }
25 }
26 }

Les deux exemples suivants montrent que plus ou moins d’instructions sont exécutées selon le
moment où la saisie est incorrecte.

> java Excep_1_1


Taille du tableau? aze
On vous demandait un nombre entier, Vous avez tapé autre chose.
Tant pis pour vous.
> java Excep_1_1
Taille du tableau? 3
Entrez un nombre: 1
Entrez un nombre: 2
Entrez un nombre: a
On vous demandait un nombre entier, Vous avez tapé autre chose.
Tant pis pour vous.

Mais dans les deux cas d’exception, les dernières instructions de la voie normale (lignes 13 à 19)
ne sont pas exécutées.

4 NFA031 – CNAM 2017


CHAPITRE 8. EXCEPTIONS 8.2. TRAITER LES EXCEPTIONS PRÉDÉFINIES

Grâce au traitement d’exception de ce programme, celui-ci ne plante plus brutalement comme


avant, mais le résultat est presque le même : le programme n’a rien fait d’utile. Il vaudrait mieux un
programme qui donne une seconde chance après une erreur de saisie.

8.2.3 Meilleur traitement d’un erreur de lecture

public class Excep_1_2{


public static void main(String[] args){
int[] tab;
int taille, indice;
Terminal.ecrireString("Taille du tableau? ");
try{
taille = Terminal.lireInt();
}catch(TerminalException e){
Terminal.ecrireString("On vous demandait un nombre entier, ");
Terminal.ecrireString("Veuillez rentrer un nombre entier: ");
taille = Terminal.lireInt();
}
tab = new int[taille];
for (int i=0; i<taille; i=i+1){
Terminal.ecrireString("Entrez un nombre: ");
try{
tab[i]=Terminal.lireInt();
}catch(TerminalException e){
Terminal.ecrireString("On vous demandait un nombre entier, ");
Terminal.ecrireString("Veuillez rentrer un nombre entier: ");
tab[i] = Terminal.lireInt();
}
}
Terminal.ecrireString("Quelle case voulez-vous doubler? ");
try{
indice = Terminal.lireInt();
}catch(TerminalException e){
Terminal.ecrireString("On vous demandait un nombre entier, ");
Terminal.ecrireString("Veuillez rentrer un nombre entier: ");
indice = Terminal.lireInt();
}
tab[indice]=tab[indice]*2;
for (int i=0; i<taille; i=i+1){
Terminal.ecrireString(tab[i]+" ");
}
Terminal.sautDeLigne();
}
}

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.

NFA031 – CNAM 2017 5


8.2. TRAITER LES EXCEPTIONS PRÉDÉFINIES CHAPITRE 8. EXCEPTIONS

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.

> java Excep_1_2


Taille du tableau? a
On vous demandait un nombre entier, Veuillez rentrer un nombre entier: a
Exception in thread "main" TerminalException
at Terminal.exceptionHandler(Terminal.java:115)
at Terminal.lireInt(Terminal.java:25)
at Excep_1_2.main(Excep_1_2.java:11)

En effet, l’instruction taille = Terminal.lireInt(); dans la liste de sinstructions du


catch n’est pas soumise à une récupération d’exception. Seules les instruction dans la partie try sont
soumises au catch.

8.2.4 Traitement amélioré des lectures


Voici comment on peut améliorer encore le traitement des saisies : il faut proposer en boucle la
lecture au clavier du nombre autant de fois que nécessaire et faire en sorte que cette saisie soit toujours
à l’intérieur du try.
public class LectureProtegee{
public static void main(String[] args){
int entier=0;
boolean correct;
do{
try{
Terminal.ecrireString("Entrez un entier: ");
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);

6 NFA031 – CNAM 2017


CHAPITRE 8. EXCEPTIONS 8.2. TRAITER LES EXCEPTIONS PRÉDÉFINIES

Terminal.ecrireIntln(entier);
}
}

Exécution :

> java LectureProtegee


Entrez un entier: a
Vous avez tapé autre chose qu’un entier Veuillez recommencer
Entrez un entier: 1.2
Vous avez tapé autre chose qu’un entier Veuillez recommencer
Entrez un entier: 12
12

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();

NFA031 – CNAM 2017 7


8.2. TRAITER LES EXCEPTIONS PRÉDÉFINIES CHAPITRE 8. EXCEPTIONS

}
}

Exécution :

> java Excep_1_3


Taille du tableau? a
Vous avez tapé autre chose qu’un entier Veuillez recommencer
Taille du tableau? 2
Entrez un nombre: 1
Entrez un nombre: 1
Quelle case voulez-vous doubler? a
Vous avez tapé autre chose qu’un entier Veuillez recommencer
Quelle case voulez-vous doubler? 1
1 2

8.2.5 Traitement de toutes les exceptions


En appliquant le même principe aux autres exceptions (NegativeArraySizeException et ArrayIn-
dexOutOfBoundsException), on obtient le programme suivant.
public class Excep_1_4{
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=0, indice;
boolean correct;
do{
try{
taille = lireEntier("Taille du tableau? ");
tab = new int[taille];
correct=true;
}catch(NegativeArraySizeException e){

8 NFA031 – CNAM 2017


CHAPITRE 8. EXCEPTIONS 8.3. DÉFINIR UNE EXCEPTION

Terminal.ecrireString("Une taille doit être positive.");


Terminal.ecrireStringln(" Veuillez recommencer");
correct = false;
}
}while(!correct);
for (int i=0; i<taille; i=i+1){
tab[i]=lireEntier("Entrez un nombre: ");
}
do{
try{
indice=lireEntier("Quelle case voulez-vous doubler? ");
tab[indice]=tab[indice]*2;
correct=true;
}catch(ArrayIndexOutOfBoundsException e){
Terminal.ecrireString("Cette case n’existe pas.");
Terminal.ecrireStringln(" Veuillez recommencer");
correct = false;
}
}while(!correct);
for (int i=0; i<taille; i=i+1){
Terminal.ecrireString(tab[i]+" ");
}
Terminal.sautDeLigne();
}
}

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 !

8.3 Définir une exception


Le programmeur peut définir des exceptions correspondant à des erreurs qui peuvent se produire
dans son programme s’il souhaite que la détection et le traitement de cette erreur soient dans des
méthodes différentes du programme.
Prenons l’exemple de la fonction factorielle. Voici un code possible pour cette fonction en java.
public class Fact_1{
public static int factorielle(int n){
int res = 1;
for (int i=n; i!=1; i=i-1){
res = res *i;
}
return res;
}
public static void main(String[] args){
int x;

NFA031 – CNAM 2017 9


8.4. LANCER UNE EXCEPTION CHAPITRE 8. EXCEPTIONS

Terminal.ecrireString("Entrez un nombre: ");


x = Terminal.lireInt();
Terminal.ecrireStringln("factorielle de " + x + " = " +
factorielle(x));
}
}

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.

8.4 Lancer une exception


Pour lancer l’exception, il faut utiliser le code suivant :
throw new FactorielleNonDefinie();

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.

8.4.1 L’exemple de la factorielle continué


Voici comment utiliser concrètement l’exception dans le cas de notre exemple.
class FactorielleNonDefinie extends RuntimeException {}
public class Fact_2{

public static int factorielle(int n){


if (n<0){
throw new FactorielleNonDefinie();
}
int res = 1;
for (int i=n; i!=1; i=i-1){
res = res *i;
}
return res;
}
public static void main(String[] args){
int x;
Terminal.ecrireString("Entrez un nombre: ");
x = Terminal.lireInt();

10 NFA031 – CNAM 2017


CHAPITRE 8. EXCEPTIONS 8.4. LANCER UNE EXCEPTION

Terminal.ecrireStringln("factorielle de " + x + " = " +


factorielle(x));
}
}

Exemple d’exécutions :

> java Fact_2


Entrez un nombre: 5
factorielle de 5 = 120
> java Fact_2
Entrez un nombre: -2
Exception in thread "main" FactorielleNonDefinie
at Fact_2.factorielle(Fact_2.java:6)
at Fact_2.main(Fact_2.java:19)

8.4.2 Attraper une exception que l’on a lancée


On peut attrapper les exceptions que l’on lance avec un try...catch de la même façon que pour les
exceptions lancées par les méthodes prédéfinies.
class FactorielleNonDefinie extends RuntimeException {}
public class Fact_3{

public static int factorielle(int n){


if (n<0){
throw new FactorielleNonDefinie();
}
int res = 1;
for (int i=n; i!=1; i=i-1){
res = res *i;
}
return res;
}
public static void main(String[] args){
int x;
boolean correct;
do{
try{
Terminal.ecrireString("Entrez un nombre: ");
x = Terminal.lireInt();
Terminal.ecrireStringln("factorielle de " + x + " = " +
factorielle(x));
correct = true;
}catch(FactorielleNonDefinie e){
Terminal.ecrireString("La factorielle n’est pas définie ");
Terminal.ecrireString("pour les nombres négatifs. ");
correct = false;

NFA031 – CNAM 2017 11


8.4. LANCER UNE EXCEPTION CHAPITRE 8. EXCEPTIONS

}
}while(!correct);
}
}

Exemple d’exécution :

> java Fact_3


Entrez un nombre: -2
La factorielle n’est pas définie pour les nombres négatifs. Entrez un nombre: 2
factorielle de 2 = 2

8.4.3 Jamais le throw directement dans le try


L’utilisation d’une exception ne se justifie que si on lance te on attrape l’exception dans deux
méthodes différentes.
En effet, si l’on fait les deux dans la même méthode, on peut utiliser plus simplement un if et c’est
préférable.
Voyons un exemple : la saisie d’un nombre compris dans un intervalle.
class HorsIntervalle extends RuntimeException{}
public class Excep_2{
public static int lireEntierIntervalle(int min, int max){
int res=0;
boolean correct;
do{
try{
Terminal.ecrireString("Entrez un nombre: ");
res = Terminal.lireInt();
if (res<min res>max){
throw new HorsIntervalle();
}
correct = true;
}catch(HorsIntervalle e){
Terminal.ecrireStringln("Nombre hors intervalle");
correct = false;
}
}while(!correct);
return res;
}
}

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{

12 NFA031 – CNAM 2017


CHAPITRE 8. EXCEPTIONS
8.5. APPEL INDIRECT DE LA MÉTHODE QUI LANCE L’EXCEPTION

Terminal.ecrireString("Entrez un nombre: ");


res = Terminal.lireInt();
if (res<min res>max){
Terminal.ecrireStringln("Nombre hors intervalle");
correct = false;
}else{
correct = true;
}
}while(!correct);
return res;
}
}

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.

8.5 Appel indirect de la méthode qui lance l’exception


Il peut y avoir une chaîne de plusieurs méthodes entre la méthode appelée dans la try...catch et la
méthode qui lance l’exception avec throw. Il peut y avoir dans le try...catch une méthode 1 et dans
le code de cette méthode une méthode 2 et dans le code de cette méthode 2 un appel à méthode 3 et
dans celle-là un throw. Il peut ainsi y avoir une chaîne d’appels.
Le principe est que si une méthode appelle une autre méthode qui lance une exception et qu’elle
n’attrape pas cette exception, c’est comme si elle la lançait elle-même et elle peut être mise dans un
try.
Voyons un exemple simplifié qui retranscrit ce qui se passe lors de la levée d’une exception.
class LException extends RuntimeException{}
public class Excep_3{
public static void methode1(int x){
System.out.println("methode 1 commence");
methode2(x);
System.out.println("methode 1 se termine");
}
public static void methode2(int x){
System.out.println("methode 2 commence");
methode3(x);
System.out.println("methode 2 se termine");
}
public static void methode3(int x){
System.out.println("methode 3 commence");
if (x==0){
throw new LException();
}
System.out.println("methode 3 se termine");
}
public static void main(String[] args){

NFA031 – CNAM 2017 13


8.5. APPEL INDIRECT DE LA MÉTHODE QUI LANCE L’EXCEPTION
CHAPITRE 8. EXCEPTIONS

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 :

> java Excep_3


Debut du main
Entier: 2
methode 1 commence
methode 2 commence
methode 3 commence
methode 3 se termine
methode 2 se termine
methode 1 se termine
Fin du try
fin du main
> java Excep_3
Debut du main
Entier: 0
methode 1 commence
methode 2 commence
methode 3 commence
Exception capturee
fin du main

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.

14 NFA031 – CNAM 2017


Chapitre 9

Tableaux à deux dimensions

Introduction aux tableaux de tableaux


Nous avons déjà vu qu’on peut utiliser un tableau pour chaque type de données Java de base
–int, double, char, boolean – ainsi que pour le type des chaînes de caractères String.
On peut également utiliser un tableau pour stocker dans chaque case, non pas une valeur simple,
mais un tableau d’un certain type. Par exemple, on peut créer un tableau contenant dans chaque case
un tableau d’entiers.
On peut représenter un tel tableau de la façon suivante :
0 1 2

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.

9.1 Créer et manipuler des tableaux de tableaux


9.1.1 Création des tableaux
On peut créer de deux façons des tableaux de tableaux : en créant en même temps le gros tableau
et les tableaux contenus dans chaque case ou en créant d’abord le grand tableau et en remplissant
ensuite les différentes case.

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]);
}
}
}

> java Tabtab


null
null
null
On peut ensuite affecter un tableau d’entier à une case. Dans l’instruction suivante, il s’agit d’un
nouveau tableau créé par new.
tab[0] = new int[2];

0 1 2

Le tableau devient alors : 0 1


null null
0 0

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

Ce qui crée le tableau suivant : 0 1 0 1 0 1


1 2 3 4 5 6

2 NFA031 – CNAM 2017


CHAPITRE 9. TABLEAUX À DEUX DIMENSIONS 9.2. TABLEAU À DEUX DIMENSIONS

9.1.2 Affectations et utilisation des valeurs


On peut utiliser un entier contenu dans une case d’un des petits tableaux ou changer la valeur d’un
de ces cases en précisant deux numéros de cases successivement : le numéro du grand tableau puis
celui du petit, chacun dans sa paire de crochets.
int[][] tab;
tab = new int[3][2];
tab[1][0] = 12;
System.out.println(tab[1][0]);

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.

9.1.3 Parcours d’un tableau de tableau


Pour parcourir toutes les cases de tous les petits tableaux, il faut deux boucles imbriquées : une
qui parcours les cases du grand tableau, l’autre qui pour chacune des cases du grand tableau, parcourt
les cases du petit tableau qu’elle contient.
Le nombre de case du grand tableau est donné par l’attribut tab.length et le nombre de cases
du petit tableau contenu dans la case numéro i est donné par l’attribut tab[i].length.
int[][] tab;
tab = new int[3][2];
tab[1][0] = 12;
for (int casegrand = 0; casegrand<tab.length; casegrand++){
for (int casepetit=0; casepetit < tab[casegrand].length;
casepetit++){
System.out.print(tab[casegrand][casepetit] + " ");
}
System.out.println();
}

9.2 Tableau à deux dimensions


Les tableaux de tableaux de java peuvent s’interpréter comme des tableaux à deux dimensions avec
des lignes et des colonnes. A ce titre, ils peuvent servir à représenter toutes les informations qu’on a
l’habitude de représenter avec des tableaux de ce genre : des planning avec les jours en colonnes et les
heures en lignes, des tableaux de ventes avec des produits en colonnes et des zonnes géographiques
en lignes, etc.

NFA031 – CNAM 2017 3


9.2. TABLEAU À DEUX DIMENSIONS CHAPITRE 9. TABLEAUX À DEUX DIMENSIONS

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

9.2.1 Représentation du tableau en Java

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.

public class Tab2d{


public static void main(String[] args){
int[][] tabTonnage_v1 = {{810,332},{4407,1219},{4398,1964},
{8245,7182},{3611,14585},{1422,13349},
{451,3834}};
int[][] tabTonnage_v2 = {{810,4407,4398,8245,3611,1422,451},
{332,1219,1964,7182,14585,13349,3834}};
System.out.println(tabTonnage_v1[3][1]);
System.out.println(tabTonnage_v2[1][3]);
}
}

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).

4 NFA031 – CNAM 2017


CHAPITRE 9. TABLEAUX À DEUX DIMENSIONS 9.2. TABLEAU À DEUX DIMENSIONS

9.2.2 Parcours des lignes et des colonnes


Comment calculer le total du tonnage coulé pendant toute la guerre ? Il faut parcourir toutes les
cases de la première ligne et faire la somme des entiers qu’elle contient. Une simple boucle permet de
parcourir les 7 colonnes.
public class Tab2d{
public static void main(String[] args){
int[][] tabTonnage_v1 = {{810,332},{4407,1219},{4398,1964},
{8245,7182},{3611,14585},{1422,13349},
{451,3834}};
int total = 0;
for (int colonne=0; colonne<tabTonnage_v1.length; colonne++){
total = total + tabTonnage_v1[colonne][0];
}
System.out.println("Tonnage coulé: " + total);
}
}

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);

On peut calculer cet indice pour chacune des années.


for (int col=0; col<tabTonnage_v1.length; col++){
System.out.println("variation du tonnage en " +
(1939+col) + ": " +
(tabTonnage_v1[col][1] -tabTonnage_v1[col][0]));
}

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];
}

9.2.3 Conclusion sur les tableaux à deux dimensions


L’usage de tableaux de tableaux pour représenter en Java des tableaux à deux dimensions est très
fréquent. C’est même l’usage principal des tableaux de tableaux.
On peut de la même façon représenter des tableaux à trois dimensions par des tableaux de tableaux
de tableaux (par exemple, type int[][][]), à quatre dimensions, etc. Jusqu’à la dimension 3 on
peut avoir une représentation graphique (quoique moins facilement qu’en dimension 2). Au-delà, il

NFA031 – CNAM 2017 5


9.3. TABLEAUX DE TABLEAUX ET MÉMOIRE
CHAPITRE 9. TABLEAUX À DEUX DIMENSIONS

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.

9.3 Tableaux de tableaux et mémoire


Si l’on déclare un tableau de la façon suivante :
int[][] tab = {{1,2},{3,4},{5,6}};

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

Mémoire privée de main int[]


args : • 0 3
tab : • 1 4

int[]
0 5
1 6

9.3.1 Tableaux non rectangulaires


Compte tenu de la structure de tableaux enchassés dans un autre tableau, rien n’oblige à ce que les
tableaux des différentes cases aient la même taille. On peut parfaitement avoir un tableau avec dans la
première case un tableau de taille 2, dans la deuxième case, un tableau de taille 3 et dans la troisième
case un tableau de taille 1. Suit la déclaration d’un tel tableau et l’état mémoire correspondant.
int[][] tab = {{1,2},{3,4,5},{6}};

6 NFA031 – CNAM 2017


CHAPITRE 9. TABLEAUX À DEUX DIMENSIONS
9.3. TABLEAUX DE TABLEAUX ET MÉMOIRE

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.

NFA031 – CNAM 2017 7


9.3. TABLEAUX DE TABLEAUX ET MÉMOIRE
CHAPITRE 9. TABLEAUX À DEUX DIMENSIONS

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.

9.3.2 Tableaux avec partage


Dans un tableau de tableaux, rien n’empêche de mettre le même petit tableau dans plusieurs case
du grand tableau. On a alors une structure étrange, que l’on ne peut pas dessiner sans prendre en
compte la notion d’adresse ou référence. Le programme suivant est un exemple de tableau à 3 cases
contenant toutes les trois le même petit tableau.
public class TabPartage{
public static void afficher(char[][] t){
for (int lig=0; lig<t[0].length; lig=lig+1){
for (int col=0; col<t.length; col=col+1){
System.out.print(t[col][lig]+" ");
}
System.out.println();
}
System.out.println();
}
public static void main(String[] args){
char[] petit = {’a’,’b’,’c’,’d’};
char[][] grand = new char[3][];
grand[0]=petit;
grand[1]=petit;
grand[2]=petit;
afficher(grand);
grand[0][1]=’X’;
afficher(grand);
}
}

L’exécution du programme produit l’affichage suivant :


> java TabPartage
a a a
b b b
c c c
d d d

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.

8 NFA031 – CNAM 2017


CHAPITRE 9. TABLEAUX À DEUX DIMENSIONS 9.4. CONCLUSION

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).

NFA031 – CNAM 2017 9


Chapitre 10

Exécution de programmes et mémoire (2)

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.

10.1 Méthodes et mémoire


Un principe fondamental du langage Java est que chaque méthode a ses propres variables et aucune
variable ne peut être partagée par deux méthodes différentes.
Pour chaque exécution d’une méthode, une portion de la pile lui est affectée pour y stocker ses
paramètres et ses variables. Ses variables sont celles qui sont déclarées dans son corps.
Le calcul d’un appel de méthode se fait en plusieurs temps :
— Calcul de la valeur des paramètres
— Affectation d’une mémoire privée dans la pile. Dans cette mémoire privée les paramètres sont
stockés avec leurs noms et leurs valeurs calculées à l’étape précédente.
— Exécution des instructions du corps de la méthode.
— Une fois l’exécution de ces instructions terminée, la mémoire privée de la méthode dans la
pile est libéré. La valeur calculée est renvoyée à la méthode contenant l’appel (sauf méthode
void, pas de valeur renvoyée dans ce cas).
Voyons un exemple d’exécution d’un programme comportant un appel à une méthode.

10.1.1 Appel de méthode : premier exemple

1 public class LeMin{


2 public static int min(int nb1, int nb2){
3 int res;
4 if (nb1<nb2){
5 res = nb1;
6 }else{

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 : •

L’exécution commence par la ligne 12 : la déclaration de la variable x conduit à l’allocation d’un


emplacement mémoire pour x à l’intérieur de la mémoire privée de main. La variable est une variable
locale à la méthode main. Elle existe à partir du moment où la ligne 12 est exécutée et son existence
se poursuivra jusqu’à la fin de l’exécution de la méthode main (fin de la ligne 16). La ligne 13 est
exécutée qui va mettre dans l’espace alloué à x la valeur 127. À la fin de l’exécution de la ligne 13 la
mémoire est donc dans l’état suivant.

Pile Tas

Mémoire privée de main String[]


args : •
x : 127

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.

2 NFA031 – CNAM 2017


CHAPITRE 10. EXÉCUTION ET MÉMOIRE 2 10.1. MÉTHODES ET MÉMOIRE

— É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

Mémoire privée de min


nb1 : 127
nb2 : 12 String[]

Mémoire privée de main


args : •
x : 127

— Étape 3 : exécution du corps de la méthode. Cela commence par la déclaration de la variable


res. Cette variable ets locale à la méthode min. La mémoire privée de cette variable est
dsituée dans la mémoire privée de la méthode min.

Pile Tas

Mémoire privée de min


nb1 : 127
nb2 : 12
String[]
res : ?

Mémoire privée de main


args : •
x : 127

L’exécution se poursuit avec le calcul de la condition du if de la ligne 4. Cette condition compare


les valeurs des paramètres nb1 et nb2. Comme 127 n’est pas plus petit que 12, la condition est fausse
et c’est le cas else qui s’exécute. La ligne 7 est exécutée : la valeur de nb2, 12, est affectée à la
mémoire privée de res. L’état est alors :

Pile Tas

Mémoire privée de min


nb1 : 127
nb2 : 12
String[]
res : 12

Mémoire privée de main


args : •
x : 127

NFA031 – CNAM 2017 3


10.1. MÉTHODES ET MÉMOIRE CHAPITRE 10. EXÉCUTION ET MÉMOIRE 2

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[]

Mémoire privée de main


args : •
x : 127

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[]

Mémoire privée de main


args : •
x : 12

La ligne 15 s’exécute : x prend la valeur 144. La ligne 16 affiche la valeur de x, 144.

10.1.2 Appel de méthode : deuxième exemple


Nous allons voir à présent un second exemple où il y a deux variables portant le même nom dans
deux méthodes différentes. Il s’agit en fait du même programme dans lequel la variable res de la
méthode min a été renommé en x.
1 public class LeMinBis{
2 public static int min(int nb1, int nb2){
3 int x;
4 if (nb1<nb2){
5 x = nb1;
6 }else{
7 x = nb2;

4 NFA031 – CNAM 2017


CHAPITRE 10. EXÉCUTION ET MÉMOIRE 2 10.1. MÉTHODES ET MÉMOIRE

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

Mémoire privée de main String[]


args : •
x : 127

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

Mémoire privée de min


nb1 : 127
nb2 : 12
String[]
x : 12

Mémoire privée de main


args : •
x : 127

On voit ici qu’il y a deux variables x avec deux valeurs différentes (127 et 12).

NFA031 – CNAM 2017 5


10.1. MÉTHODES ET MÉMOIRE CHAPITRE 10. EXÉCUTION ET MÉMOIRE 2

10.1.3 Trois méthodes

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.

1 public class MinDe3{


2 public static int min2(int nb1, int nb2){
3 int res;
4 if (nb1<nb2){
5 res = nb1;
6 }else{
7 res = nb2;
8 }
9 return res;
10 }
11 public static int min3(int nb1, int nb2, int nb3){
12 int res;
13 res = min2(nb2,nb3);
14 if (nb1<res){
15 res = nb1;
16 }
17 return res;
18 }
19 public static void main(String[] args){
20 int x;
21 x = 127;
22 x = min3(x,12,x+1);
23 x = x *x;
24 System.out.println(x);
25 }
26 }

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

Mémoire privée de main String[]


args : •
x : 127

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.

6 NFA031 – CNAM 2017


CHAPITRE 10. EXÉCUTION ET MÉMOIRE 2 10.1. MÉTHODES ET MÉMOIRE

Pile Tas

Mémoire privée de min3


nb1 : 127
nb2 : 12
nb3 : 128 String[]

Mémoire privée de main


args : •
x : 127

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

Mémoire privée de min3


nb1 : 127
nb2 : 12
nb3 : 128 String[]
res : ?

Mémoire privée de main


args : •
x : 127

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.

NFA031 – CNAM 2017 7


10.1. MÉTHODES ET MÉMOIRE CHAPITRE 10. EXÉCUTION ET MÉMOIRE 2

Pile Tas

Mémoire privée de min2


nb1 : 12
nb2 : 128

Mémoire privée de min3


String[]
nb1 : 127
nb2 : 12
nb3 : 128
res : ?

Mémoire privée de main


args : •
x : 127

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

Mémoire privée de min2


nb1 : 12
nb2 : 128
res : :?

Mémoire privée de min3


String[]
nb1 : 127
nb2 : 12
nb3 : 128
res : ?

Mémoire privée de main


args : •
x : 127

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.

8 NFA031 – CNAM 2017


CHAPITRE 10. EXÉCUTION ET MÉMOIRE 2 10.1. MÉTHODES ET MÉMOIRE

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.

Continuons l’exécution avec la ligne 4 : il y a comparaison de nb1 et nb2. Dans la mémoire de


sommet de pile, on rouve respectivement 12 et 128. 12 est plus petit que 128, donc c’est la condition
est vraie et c’est la première branche du if (ligne 5) qui est exécutée. C’est la valeur de nb1, 12, qui
est mise dans res.

Pile Tas

Mémoire privée de min2


nb1 : 12
nb2 : 128
res : : 12

Mémoire privée de min3


String[]
nb1 : 127
nb2 : 12
nb3 : 128
res : ?

Mémoire privée de main


args : •
x : 127

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.

NFA031 – CNAM 2017 9


10.1. MÉTHODES ET MÉMOIRE CHAPITRE 10. EXÉCUTION ET MÉMOIRE 2

Pile Tas

Mémoire privée de min3


nb1 : 127
String[]
nb2 : 12
nb3 : 128
res : ?

Mémoire privée de main


args : •
x : 127

La ligne 13 peut enfin se terminer. La valeur 12 retournée par min2 est mise dans res.

Pile Tas

Mémoire privée de min3


nb1 : 127
String[]
nb2 : 12
nb3 : 128
res : 12

Mémoire privée de main


args : •
x : 127

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.

10 NFA031 – CNAM 2017


CHAPITRE 10. EXÉCUTION ET MÉMOIRE 2 10.2. PASSAGE PARAMÈTRE

Pile Tas

String[]

Mémoire privée de main


args : •
x : 127

La ligne 22 peut s’achever : la valeur 12 est mise dans x.

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.

10.2 Passage de paramètre : valeur primitive ou adresse


Une méthode ne peut pas changer le contenu de la mémoire d’une autre méthode. Mais elle peut
modifier dans le tas un espace mémoire accessible depuis une autre méthode. Nous pouvons voir la
différence entre les valeurs primitives stockées dans la pile et les tableaux stockés dans le tas dans
l’exemple suivant.
1 public class ChangeXEtTab{
2 public static void main(String[] args){
3 int x = 4;
4 int[] tab = {4, 8, 12};
5 changeXEtTab(x,tab);
6 System.out.println(x);
7 System.out.println(tab[0]);
8 }
9 public static void changeXEtTab(int valint, int[] valtab){
10 valint = valint *10;
11 valtab[0] = valtab[0] *10;

NFA031 – CNAM 2017 11


10.2. PASSAGE PARAMÈTRE CHAPITRE 10. EXÉCUTION ET MÉMOIRE 2

12 }
13 }

L’exécution du programme commence ligne 3. Après axécution des lignes 3 et 4, l’état de la


mémoire est le suivant.

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.

12 NFA031 – CNAM 2017


CHAPITRE 10. EXÉCUTION ET MÉMOIRE 2 10.2. PASSAGE PARAMÈTRE

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 : •

NFA031 – CNAM 2017 13


10.2. PASSAGE PARAMÈTRE CHAPITRE 10. EXÉCUTION ET MÉMOIRE 2

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++.

10.2.2 Agrandissement d’un tableau


On pourrait avoir l’impression que toute opération faite sur un tableau passé en paramètre dans
une méthode change nécessairement le tableau dans la méthode appelante. Or ce n’est pas le cas
comme va le montrer l’exemple suivant. On veut écrire une méthode qui va ajouter une valeur dans un
tableau et pour ce faire va ajouter une case de plus. Comme il n’est pas possible d’agrandir un tableau
existant, la méthode va en fait créer un nouveau tableau plus grand, recopier les valeurs du premier
tableau et ajouter la nouvelle valeur dans la case en plus.

1 public class Agrandir{


2 public static void agrandir(char[] tab, char nv){
3 char[] res = new char[tab.length+1];
4 for (int i=0; i<tab.length; i++){
5 res[i]=tab[i];
6 }
7 res[tab.length]=nv;
8 tab = res;
9 }
10 public static void main(String[] args){
11 char[] tab = {’a’, ’b’};
12 agrandir(tab,’c’);
13 for (int i=0; i<tab.length; i++){
14 System.out.print(tab[i]);
15 }
16 System.out.println();
17 }
18 }

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.

14 NFA031 – CNAM 2017


CHAPITRE 10. EXÉCUTION ET MÉMOIRE 2 10.2. PASSAGE PARAMÈTRE

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

Mémoire privée d’agrandir String[]


tab : •
nv : ’c’
char[]
Mémoire privée de main 0 ’a’
args : • 1 ’b’
tab : •

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[]

Mémoire privée d’agrandir


tab : • char[]
nv : ’c’ 0 ’a’
res : • 1 ’b’

Mémoire privée de main char[]


args : • 0 ”
tab : • 1 ”
2 ”

NFA031 – CNAM 2017 15


10.2. PASSAGE PARAMÈTRE CHAPITRE 10. EXÉCUTION ET MÉMOIRE 2

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[]

Mémoire privée d’agrandir


tab : • char[]
nv : ’c’ 0 ’a’
res : • 1 ’b’

Mémoire privée de main char[]


args : • 0 ’a’
tab : • 1 ’b’
2 ’c’

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[]

Mémoire privée d’agrandir


tab : • char[]
nv : ’c’ 0 ’a’
res : • 1 ’b’

Mémoire privée de main char[]


args : • 0 ’a’
tab : • 1 ’b’
2 ’c’

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.

16 NFA031 – CNAM 2017


CHAPITRE 10. EXÉCUTION ET MÉMOIRE 2 10.2. PASSAGE PARAMÈTRE

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.

> java Agrandir


ab

10.2.3 Formulation du phénomène observé


Le fait qu’une affectation ait une portée locale à une méthode ou affecte plusieurs méthodes ne dé-
pend pas fondamentalement du type de la variable mais de l’emplacement mémoire qui est concerné
par l’affectation. Si l’affectation change le contenu d’un espace mémoire dans la pile, c’est nécessaire-
ment local à une méthode et ne change rien aux variables des autres méthodes. Si l’affectation change
le contenu d’un espace mémoire dans le tas (typiquement une case de tableau), cela peut changer la
valeurs de variables situées dans d’autres méthodes. Pour savoir si une affectation désigne un espace
dans la pile ou dans le tas, il suffit de regarder la partie gauche de l’affectation. Si c’est un nom de
variable seul, c’est dans la pile. Si c’est un nom de variable suivi d’une valeur entre crochets, c’est
dans le tas. tab= ... change la pile, tab[0]= ... change le tas.

10.2.4 Comment agrandir un tableau dans une méthode


Comme nous l’avons vu, il n’est pas possible d’agrandir un tableau par modification d’un tableau
passé en paramètre. Un nouveau tableau créé dans la procédure ne peut pas être connu dans la méthode
appelante (dans notre exemple, le main) si ce n’est en renvoyant ce tableau comme résultat de la
méthode. Ce résultat peut être affecté à la variable adéquate dans la méthode main.
public class Agrandir2{
public static char[] agrandir(char[] tab, char nv){
char[] res = new char[tab.length+1];
for (int i=0; i<tab.length; i++){
res[i]=tab[i];
}

NFA031 – CNAM 2017 17


10.3. TABLEAUX À DEUX DIMENSIONS CHAPITRE 10. EXÉCUTION ET MÉMOIRE 2

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 :

> java Agrandir2


abc

10.3 Tableaux à deux dimensions

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.

Si l’on déclare un tableau de la façon suivante :

int[][] tab = {{1,2},{3,4},{5,6}};

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].

18 NFA031 – CNAM 2017


CHAPITRE 10. EXÉCUTION ET MÉMOIRE 2 10.4. CONCLUSION DU CHAPITRE

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.

10.4 Conclusion du chapitre


Les références ou adresses dans le tas jouent un rôle important en Java et font que les tableaux et
les String se comportent différemment des valeurs des types primitifs (int, double, boolean, char).
— les tableaux et les String doivent être créés (opération new) avant d’exister. Les int et les
boolean n’ont pas à être créés.
— les mémoires privées des tableaux et String sont dans le tas. Les int et boolean n’ont pas de
mémoire privée.
— une variable de type int contient directement la valeur, une variable de type tableau contient
l’adresse de la mémoire privée du tableau.
— un tableau passé en paramètre peut être modifié par la méthode appelée. Ce n’est pas le cas
pour des valeurs primitives passées en paramètres.

NFA031 – CNAM 2017 19


Chapitre 11

Représenter des données avec des


tableaux

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.

11.1 Un problème de taille


Il arrive couramment que l’on doive représenter une collection de données dont la taille varie
au fil du temps, avec des opérations d’ajout et de suppression. Par exemple, le nombre d’employés
d’une entreprise augmente avec les embauches et diminue avec les démissions, départs en retraite et
licenciements. Dans un répertoire téléphonique, des contacts peuvent être ajoutés et supprimés. Des
produits peuvent être ajoutés ou retirés du catalogue.
Lorsqu’on utilise un tableau pour stocker une telle collection, il se pose le problème d’adapter le
tableau à la taille des données. En Java, les tableaux ne changent jamais de taille, mais les variables
peuvent désigner successivement des tableaux de différentes tailles.
Dans la suite du chapitre, nous allons prendre l’exemple d’un tableau de nombres entiers et les
deux opérations d’ajout d’un nombre ou de retrait d’un nombre.

11.1.1 Adapter la taille du tableau


Il est possible de mettre la liste dans une variable et de créer un nouveau tableau à chaque ajout
et chaque retrait et affecter ce nouveau tableau à la variable. Il faut ensuite recopier les nombres de
l’ancien au nouveau tableau.
public class SDD1{
public static void main(String[] args){
int[] liste = {474, 317, 215, 700, 895, 999};
// retirer un nombre
int indice = 0, nb;
for (int i=0; i<liste.length; i=i+1){
System.out.println(liste[i]+ " ");
}
System.out.println();

1
11.1. UN PROBLÈME DE TAILLE CHAPITRE 11. TABLEAUX ET DONNÉES

System.out.println("Quel nombre voulez-vous retirer?");


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{
int[] nouveau = new int[liste.length-1];
// recopier les noms avant le nombre à supprimer: même indice
for (int i=0; i<indice; i++){
nouveau[i]=liste[i];
}
// recopier les noms après nb: une case plus à gauche dans le nouveau
for (int i=indice+1; i<liste.length; i++){
nouveau[i-1]=liste[i];
}
// le nouveau tableau est prêt, on l’affecte à liste
liste = nouveau;
System.out.println("nombre supprimé");
}
// ajouter un nombre
System.out.println("Quel nombre ajouter?");
nb = Terminal.lireInt();
int[] nouveau = new int[liste.length+1];
// recopier tous les noms dans le nouveau tableau
for (int i=0; i<liste.length; i=i+1){
nouveau[i]=liste[i];
}
// ajouter nb dans la dernière case
nouveau[nouveau.length-1]=nb;
// le nouveau tableau est prêt, on l’affecte à liste
liste = nouveau;
// affichage de la liste
for (int i=0; i<liste.length; i=i+1){
System.out.println(liste[i]+ " ");
}
System.out.println();
}
}

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.

2 NFA031 – CNAM 2017


CHAPITRE 11. TABLEAUX ET DONNÉES 11.1. UN PROBLÈME DE TAILLE

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.

11.1.2 Gérer des cases vides : valeur spécifique


Une case d’un tableau ne peut pas vraiment être vide : elle contient toujours une valeur. En effet,
une case de tableau est toujours une suite de bits en mémoire, chaque bit étant à 0 ou à 1 et une telle
suite est toujours une valeur du type java correspondant. Dans notre exemple, dans un tableau d’entier
il y a toujours un entier.
Une première façon de gérer les cases vides est d’utiliser une certaine valeur entière pour repré-
senter le fait que la case est vide. On peut être tenté d’utiliser 0, parce que c’est la valeur par défaut des
cases de tableaux d’entiers. Mais dans beaucoup de cas, 0 est une valeur que l’on peut vouloir mettre
dans le tableau. Par exemple, s’il s’agit de notes, 0 est une note possible (bien que peu souhaitable !).
On ne pourra donc pas utiliser 0 pour les cases vides. Dans ce cas, on pourra utiliser -1 car -1 n’est
pas une note.
Dans d’autres applications où -1 peut être une valeur à stocker (par exemple s’il s’agit de tempé-
rature de l’air ambiant), il faudra utiliser une autre valeur. Par exemple 1000 car l’air ambiant ne peut
pas être à 1000 degrés. Une solution qui marche souvent consiste à utiliser le plus petit de tous les
entiers du type int. Il se note Integer.MIN_VALUE et il vaut -2147483648.
Pour ajouter un élément, on va parcourir le tableau jusqu’à la première case libre (qui contient
Integer.MIN_VALUE) et y mettre le nombre à ajouter. Pour supprimer un élément, on va le remplacer
dans le tableau par Integer.MIN_VALUE.
public class SDD2{
public static void main(String[] args){
// créer une liste avec 100 cases
int[] init = {474, 317, 215, 700, 895, 999};
int[] liste = new int[100];
// mettre les nombres en début de liste
for (int i=0; i<init.length; i=i+1){
liste[i]=init[i];
}
// Remplir les autres cases (qui sont vides) avec Integer.MIN_VALUE
for (int i=init.length; i<liste.length; i=i+1){
liste[i]=Integer.MIN_VALUE;
}
// retirer un nombre
int indice = 0, nb;
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();
System.out.println("Quel nombre voulez-vous retirer?");

NFA031 – CNAM 2017 3


11.1. UN PROBLÈME DE TAILLE CHAPITRE 11. TABLEAUX ET DONNÉES

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();
}
}

11.1.3 Gérer des cases vides : regrouper les cases occupées


Une autre façon courante de gérer un tableau avec des cases vides consiste à occuper les premières
cases du tableau et laisser libre celles de la fin du tableau. Entre les deux, une frontière qui correspond
au nombre de cases occupées.
0 1 2 n-1 n k-1
X X X ... X _ ... _
Ce schéma représente un tableau de taille k avec n cases occupées (figurées avec un X dedans) et
des cases libres (figurées avec un souligné).
S’il y a n cases occupées dans un tel tableau, ce sont les cases numérotées de 0 à n-1 et la case
numéro n est la première case libre. Si l’on doit ajouter un nouvel élément, ce sera dans cette nième
case. Si l’on doit supprimer un élément, il faudra peut-être et même probablement ré-aménager le
tableau car il faudra libérer la n-1ième case alors que l’élément supprimé n’était peut-être pas là. Il
faut donc procéder en deux temps : recopier l’élément qui était dans la n-1ième case dans la case

4 NFA031 – CNAM 2017


CHAPITRE 11. TABLEAUX ET DONNÉES 11.1. UN PROBLÈME DE TAILLE

occupée par l’élément à supprimer. Puis libérer la n-1ième case.


La structure de donnée sera composée de deux variables : le tableau et le nombre de cases occupées
dans le tableau (ce nombre sera dans une variable de type int).

public class SDD3{


public static void main(String[] args){
// créer une liste avec 100 cases
int[] init = {474, 317, 215, 700, 895, 999};
int[] liste = new int[100];
int nbelem = init.length;
// mettre les nombres en début de liste
for (int i=0; i<init.length; i=i+1){
liste[i]=init[i];
}
// retirer un nombre
int indice = 0, nb;
for (int i=0; i<nbelem; i=i+1){
// on n’affiche que les cases occupées
System.out.print(liste[i]+ " ");
}
System.out.println();
System.out.println("Quel nombre voulez-vous retirer?");
nb = Terminal.lireInt();
// recherche du numéro de case du nombre
while(indice<nbelem && liste[indice]!=nb){
indice=indice+1;
}
if (indice == nbelem){ // 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]=liste[nbelem-1];
nbelem=nbelem-1;
System.out.println("nombre supprimé");
}
// ajouter un nombre
System.out.println("Quel nombre ajouter?");
nb = Terminal.lireInt();
// remplir la première case vide avec le nombre
liste[nbelem]=nb;
nbelem = nbelem+1;
// affichage de la liste
for (int i=0; i<nbelem; i=i+1){
System.out.print(liste[i]+ " ");
}
System.out.println();
}
}

NFA031 – CNAM 2017 5


11.1. UN PROBLÈME DE TAILLE CHAPITRE 11. TABLEAUX ET DONNÉES

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.

11.1.4 Structure de données et méthodes


La structure de données utilisée pour la gestion des cases libres comporte deux variables indis-
sociables : le tableau et le nombre d’élément (liste et nbelem dans le code de l’exemple). Si on
n’a que le nombre d’éléments, on ne connaît pas les nombres ; si on n’a que le tableau, on ne sait pas
quelles cases sont libres ou occupées. Les deux informations sont nécessaires et leur cohérence doit
être respectée par le code du programme.
On voudrait pouvoir faire des opérations sur les structures de données au moyen de méthodes :
l’ajout, la suppression, l’affichage de la structure, etc. Les opérations qui modifient le nombre d’élé-
ments posent un problème : une méthode peut modifier le contenu d’un tableau, elle ne peut pas
modifier une variable de type int comme nbelem.
Voyons cela avec le code suivant.
public class SDD3Bis{
public static void ajouter(int[] liste, int nb, int elem){
liste[nb]=elem;
nb=nb+1;
}
public static void afficher(int[] liste, int nb){
if (nb == 0){
System.out.print("La liste est vide");
}
for (int i=0; i<nb; 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);
}
}

Lorsqu’on exécute ce code, cela donne :


> java SDD3Bis
La liste est vide
L’ajout de l’élément 474 par la méthode ajouter ne s’est pas effectué correctement. Il y a deux
affectations dans cette méthode. La première modifie le contenu de la case nb (dans le premier appel,
la case 0) du tableau liste. Cette case est la même pour le paramètre liste d’ajouter et la variable liste de

6 NFA031 – CNAM 2017


CHAPITRE 11. TABLEAUX ET DONNÉES 11.1. UN PROBLÈME DE TAILLE

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

Contexte de ajouter int[]


liste adr1 adr1 0 474
nb 1 1 0
elem 474 ...
Contexte de main
liste adr1 99 0
nbelem 0

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);
}
}

L’exécution du programme est correcte.


> java SDD3Ter
474

NFA031 – CNAM 2017 7


11.2. AVOIR UN TABLEAU TRIÉ CHAPITRE 11. TABLEAUX ET DONNÉES

Le dessin de la mémoire en fin d’exécution de la méthode ajouter est le suivant.


Pile Tas

Contexte de ajouter int[]


liste adr1 adr1 0 474
nb adr2 1 0
elem 474 ...
Contexte de main
liste adr1 99 0
nbelem adr2
int[]
adr2 0 1

11.2 Avoir un tableau trié


On peut maintenir un certain ordre dans un tableau sans avoir besoin de le trier : il suffit de partir
d’un tableau vide et de tenir compte de l’ordre lorsqu’on insère un nouvel élément.
Cette façon de procéder est intéressante parce que l’opération de tri est complexe et coûteuse.
Lorsqu’on veut insérer un élément dans un tableau trié, il faut trouver sa place dans le tableau et
pour ce faire, le comparer aux éléments déjà présents.

11.2.1 Procédure d’insertion qui assure l’ordre des éléments


Cette recherche est simple : on compare l’élément à insérer à tous les éléments successifs du
tableau jusqu’à trouver le premier élément qui est plus grand. C’est à la place de ce premier élément
plus grand qu’il faut insérer le nouvel arrivant. Mais avant cela, il faut décaler tous les éléments plus
grand d’un cran vers la droite pour lui faire une place.
Dans cet exemple, nous utilisons la gestion de cases libres avec le compteur de nombres d’élé-
ments.
public class SDD4{
// procédure qui insère elem dans tab en respectant l’ordre croissant
public static void inserer(int[] tab, int[] nbelem, int elem){
int indice = 0;
while(indice<nbelem[0] && elem>tab[indice]){
indice = indice+1;
}
// indice est le numéro où insérer elem
// il faut d’abord décaler les éléments plus grands
// vers la droite
for (int i=nbelem[0]; i>indice; i=i-1){
tab[i]=tab[i-1];
}
tab[indice]=elem;
nbelem[0]=nbelem[0]+1;

8 NFA031 – CNAM 2017


CHAPITRE 11. TABLEAUX ET DONNÉES 11.2. AVOIR UN TABLEAU TRIÉ

}
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.

11.2.2 Retirer un élément en conservant le tableau trié


Lorsqu’on retire un élément du tableau, il faut remplir la case qu’il occupait avec un autre élément
sans perturber l’ordre des éléments du tableau. Il faut donc décaler tous les éléments plus grands que
celui qu’on supprime d’un cran vers la gauche.
public class SDD4Bis{

NFA031 – CNAM 2017 9


11.2. AVOIR UN TABLEAU TRIÉ CHAPITRE 11. TABLEAUX ET DONNÉES

// procédure qui insère elem dans tab en respectant l’ordre croissant


public static void inserer(int[] tab, int[] nbelem, int elem){
int indice = 0;
while(indice<nbelem[0] && elem>tab[indice]){
indice = indice+1;
}
// indice est le numéro où insérer elem
// il faut d’abord décaler les éléments plus grands
// vers la droite
for (int i=nbelem[0]; i>indice; i=i-1){
tab[i]=tab[i-1];
}
tab[indice]=elem;
nbelem[0]=nbelem[0]+1;
}
// procédure qui supprime un élément en maintenant l’ordre
// le résultat renvoyé dit si la suppression a été faite
public static boolean supprimer(int[] tab, int[] nbelem, int elem){
int indice = 0;
while(indice<nbelem[0] && elem>tab[indice]){
indice = indice+1;
}
if (indice==nbelem[0] elem!=tab[indice]){
// l’élément n’a pas été trouvé dans le tableau
return false;
}else{
// décaler les éléments plus grands
for (int i =indice; i<nbelem[0]; i=i+1){
tab[i]=tab[i+1];
}
// diminuer le nombre d’éléments
nbelem[0]=nbelem[0]-1;
return true;
}
}
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

10 NFA031 – CNAM 2017


CHAPITRE 11. TABLEAUX ET DONNÉES 11.3. COORDONNER PLUSIEURS TABLEAUX

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);
}
}

11.3 Coordonner plusieurs tableaux

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.

NFA031 – CNAM 2017 11


11.3. COORDONNER PLUSIEURS TABLEAUX CHAPITRE 11. TABLEAUX ET DONNÉES

nom année
personne 1 0 nom 1 0 année 1
personne 2 1 nom 2 1 année 2
... 2 ... 2 ...

public class SDD6{


public static void ajouter(String[] nom, int[] annee, int[] nb,
String n, int a){
nom[nb[0]]=n;
annee[nb[0]]=a;
nb[0]=nb[0]+1;
}
public static boolean supprimer(String[] nom, int[] annee, int[] nb,
String n, int a){
int indice = 0;
while(indice<nb[0] && (n!=nom[indice] a!=annee[indice])){
indice = indice+1;
}
if (indice<nb[0] && n==nom[indice] && a==annee[indice]){
for (int i=indice; i<nb[0]; i=i+1){
nom[i]=nom[i+1];
annee[i]=annee[i+1];
}
nb[0]=nb[0]-1;
return true;
}else{
return false;
}
}
public static void afficher(String[] nom, int[] annee, int[] nb){
for (int i=0; i<nb[0]; i=i+1){
System.out.println(nom[i]+" "+annee[i]);
}
}
public static void main(String[] args){
String[] lesnoms = new String[30];
int[] lesannees = new int[30];
int[] nbpers = {0};
ajouter(lesnoms,lesannees,nbpers,"riri",1960);
ajouter(lesnoms,lesannees,nbpers,"fifi",1960);
ajouter(lesnoms,lesannees,nbpers,"loulou",1960);
ajouter(lesnoms,lesannees,nbpers,"donald",1939);
afficher(lesnoms,lesannees,nbpers);
System.out.println("----------------------------");
supprimer(lesnoms,lesannees,nbpers,"fifi",1960);
afficher(lesnoms,lesannees,nbpers);
}

12 NFA031 – CNAM 2017


CHAPITRE 11. TABLEAUX ET DONNÉES 11.3. COORDONNER PLUSIEURS TABLEAUX

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.

nom prénom année


personne 1 0 nom 1 prénom 1 0 année 1
personne 2 1 nom 2 prénom 2 1 année 2
... 2 ... ... 2 ...

NFA031 – CNAM 2017 13


Chapitre 12

Utilisation d’objets : String et ArrayList

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.

12.1 Ce que l’on sait déjà de String


Il existe en java un type prédéfini pour les chaînes de caractères avec une syntaxe spéciale. Ce type
s’appelle String et pour écrire une chaîne, on place les caractères de la chaîne entre guillemets. Il
existe pour ce type un opérateur qui s’appelle la concaténation et qui s’écrit +. La concaténation
permet de créer une chaîne en collant bout à bout deux chaînes existantes. Par exemple "to"+"to"
crée la chaîne "toto".
Par extension on peut concaténer une chaîne avec une valeur d’un type de base (int, boolean,
double ou char). Il y a une conversion de type implicite et le résultat est une chaîne de caractère. Par
exemple "resultat: "+125 crée la chaîne : "resultat: 125".

12.2 Naissance et vie des chaînes de caractères


Le type String existe dès l’origine dans le système java, mais ce n’est pas le cas des valeurs de ce
type, c’est à dire des chaînes particulières que l’on peut utiliser. Par exemple, la chaîne "bonjour"
n’existe pas au démarrage du système : si on veut l’utiliser il faut d’abord la créer. Pour cela, il existe
quatre moyens :
— en écrivant cette chaîne avec ses guillemets dans le programme.
— au moyen de l’instruction new.
— au moyen de l’opérateur + : le résultat d’une concaténation est une nouvelle chaîne qui n’exis-
tait pas avant.
— au moyen d’une méthode qui renvoie une nouvelle chaîne.
Il existe plusieurs façons de créer une chaîne avec un new. Nous allons utiliser celle qui consiste
à spécifier dans un tableau de caractères le contenu de la chaîne à créer.

char[] tab = {’b’,’o’,’n’,’j’,’o’,’u’,’r’};


String s = new String(tab);

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 = new String(new char[]{’b’,’o’,’n’,’j’,’o’,’u’,’r’});

Une fois la chaîne créée, on peut l’afficher au moyen de Terminal.ecrireString et l’uti-


liser comme opérande de l’opérateur de concaténation. On peut également appeler des méthodes.
Jusqu’à présent, les méthodes que nous avons écrites sont des méthodes des classes, déclarées avec le
mot clé static et que l’on appelle avec le nom de la classe, un point et le nom de méthode et les
paramètres entre parenthèses. Par exemple dans Terminal.ecrireString(s), Terminal est
le nom de la classe. Avec les chaînes de caractères, nous allons commencer à utiliser des méthodes
des objets. Pour les appeler, il faut avant le point non pas un nom de classe, mais un objet, une valeur.
Prenons un premier exemple : la méthode length() renvoie la longueur de la chaîne. Elle ne
prend pas de paramètre. La longueur est le nombre de caractères de la chaîne. Cette méthode n’a de
sens que pour une chaîne donnée ; on l’appelle en mettant une chaîne ou un moyen d’en calculer une,
avant le point et le nom de la méthode.

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.

12.3 Les chaînes ressemblent un peu aux tableaux


Par certains côtés, les chaînes de caractères ressemblent aux tableaux plus qu’aux types de base
tels que int ou char.
Voici les points communs entre tableaux et chaînes :
— il y a deux temps différents : la déclaration et la création d’une valeur.
— il y a possibilité de création explicite d’une nouvelle chaîne au moyen d’une instruction new.
— comme pour les tableaux, il y a possibilité de création implicite au moyen d’une syntaxe
spéciale. Pour les tableaux, avec les accolades, pour les chaînes, avec les guillemets.
— ce sont des structures regroupant plusieurs valeurs dans un certain ordre.
— plusieurs noms différents peuvent être donnés à une même structure.
— les chaînes et les tableaux ont une longueur et cette longueur est supérieure ou égale à 0. Elle
est fixe et invariable dans le temps.
— en revanche, une variable donnée peut contenir successivement des structures de tailles diffé-
rentes.
Contrairement aux tableaux :

2 NFA031 – CNAM 2017


CHAPITRE 12. UTILISER DES OBJETS 12.4. QUELQUES MÉTHODES INTÉRESSANTES

— 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.

12.4 Quelques méthodes intéressantes


Voici quelques méthodes intéressantes :
— charAt(int n) : cette méthode renvoie le nième caractère de la chaîne, la numérotation
commence à 0. Par exemple, si s est une String, s.charAt(0) renvoie le premier carac-
tère (type char) de s.
— toCharArray() permet de transformer une chaîne en un tableau de char. Par exemple, si
s est la String "bonjour" , s.toCharArray() renvoie un tableau de 7 char :
0 1 2 3 4 5 6
’b’ ’o’ ’n’ ’j’ ’o’ ’u’ ’r’
— compareTo(String s2) : compare deux chaînes selon l’ordre lexicographique (l’ordre
du dictionnaire). Si s1 et s2 sont deux String, s1.compareTo(s2) renvoie un entier.
Cet entier est négatif si s1 est plus petit que s2, positif si s1 est plus grand que s2, et 0 si
s1 et s2 sont égales.
— s1.toLowerCase() et s1.toUpperCase() renvoient une nouvelle chaîne égale à s1
mais avec toutes les lettres en minuscule et en majuscule respectivement.
— trim() : renvoie une chaîne dans laquelle les espaces en début et en fin de chaîne ont été sup-
primés. Par exemple " truc chose ".trim() renvoie la chaîne "truc chose".
— split(String s) : découpe la chaîne en plusieurs morceaux en utilisant la chaîne s
comme séparateur. Le résultat est un tableau de chaînes. Par exemple "un;deux;trois".split(";")
renvoie le tableau {"un","deux","trois"}.
— indexOf(String s) : renvoie l’indice de la première occurrence de la chaîne s dans la
chaîne. Par exemple "un deux trois".indexOf("deux") renvoie 3, car la chaîne
"deux" commence à l’indice 3 de la chaîne "un deux trois".
— substring(int debut, int fin) : renvoie la sous-chaîne de la chaîne sur laquelle
la méthode est appelée comprise entre les indices debut et fin. Le caractère d’indice debut
est inclus dans le résultat, mais pas celui d’indice fin. Par exemple "bonjour".substring(2,4)
renvoie la sous-chaîne "nj". Autrement dit, elle renvoie la sous-chaîne comprenant les carac-
tères d’indice 2 et 3.
public class ExChaine2{
public static void main (String [] arguments){
String s1 = "bonjour";
String s2;
Terminal.ecrireString("Entrez une chaine: ");
s2 = Terminal.lireString();
Terminal.ecrireCharln(s1.charAt(0));
Terminal.ecrireStringln(s1.toUpperCase());
Terminal.ecrireIntln(s1.compareTo(s2));
Terminal.ecrireStringln("" + s1.equals(s2));
}

NFA031 – CNAM 2017 3


12.5. VARIABLES ET INITIALISATIONS CHAPITRE 12. UTILISER DES OBJETS

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.

12.5 Variables et initialisations


Une valeur spéciale, null, est utilisable pour dire qu’une variable String ne pointe vers aucun
objet. Ça n’est pas la même chose que “non initialisé”.
Aucune méthode ne peut s’exécuter sur une variable qui contient null. Si l’on essaie de l’appeler
cela provoque une erreur à la compilation ou à l’exécution.
Par exemple, dans le code suivant, s est non initialisée. le programme ne compilatera pas.
public class VNI{
public static void main(String[] args){
String s;
Terminal.ecrireIntln(s.length());
}
}

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());
}

4 NFA031 – CNAM 2017


CHAPITRE 12. UTILISER DES OBJETS 12.6. COMPARAISON DE CHAÎNES

s = null;
if (s!=null){
Terminal.ecrireIntln(s.length());
}
}
}

12.6 Comparaison de chaînes


Comme pour un tableau, les opérateurs == et != ne regardent pas le contenu des chaînes mais
juste leur adresse en mémoire. La question posée est : les deux chaînes sont-elles à la même adresse ?
Ceci est illustré par le programme suivant.
public class EgCh{
public static void main(String[] args){
String s1, s2, s3;
boolean b1, b2;
s1 = "tati";
s2 = "ta";
s2 = s2 + "ti";
s3 = s1;
Terminal.ecrireStringln("s1=" + s1 + " s2=" + s2 + " s3=" + s3);
b1 = (s1 == s2);
b2 = (s1 == s3);
Terminal.ecrireStringln("s1 == s2? " + b1);
Terminal.ecrireStringln("s1 == s3? " + b2);
}
}

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.

NFA031 – CNAM 2017 5


12.7. PARAMÈTRE DE LA MÉTHODE MAIN CHAPITRE 12. UTILISER DES OBJETS

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.

12.7 Paramètre de la méthode main


Depuis le début de l’année, nous utilisons systématiquement la méthode main avec un paramètre
de type String[], c’est à dire un tableau de chaînes de caractères. Ce paramètre permet de trans-
férer des informations entre la ligne de commande et le programme java. Prenons un exemple où le
programme se contente d’afficher les valeurs passées sur la ligne de commande.
public class LigneCommande{
public static void main(String[] args){

6 NFA031 – CNAM 2017


CHAPITRE 12. UTILISER DES OBJETS
12.8. CONVERSION ENTRE CHAÎNES ET AUTRES TYPES

for (int i=0; i < args.length; i++){


Terminal.ecrireStringln(args[i]);
}
}
}

Voici un exemple d’exécution :

> java LigneCommande un deux trois


un
deux
trois
0 1 2
La tableau args dans cette exécution a trois cases. Sa valeur est
¨un¨ ¨deux¨ ¨trois¨
Notons que même si l’on passe un nombre en paramètre, celui-ci est contenu dans le tableau sous
forme d’une chaîne.

> java LigneCommande un 12 56 deux


un
12
56
deux

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.

12.8 Conversion entre chaînes et autres types


Il est parfois utile de convertir une chaîne de caractère en une valeur d’un autre type. Par exemple,
on peut vouloir transformer une chaîne qui ne contient que des chiffres en un nombre entier. Pour
réaliser la conversion, il faut utiliser la méthode Integer.parseInt et lui donner en paramètre la
chaîne à convertir.
public class StringInt2{
public static void main(String[] args){
int x;
String s = "12";
x = Integer.parseInt(s);
Terminal.ecrireIntln(x);
}
}

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.

NFA031 – CNAM 2017 7


12.9. PRÉSENTATION DE LA CLASSE ARRAYLIST CHAPITRE 12. UTILISER DES OBJETS

12.9 Présentation de la classe ArrayList


Un programme a souvent besoin de pouvoir gérer une suite d’éléments (la liste des produits com-
mandés par un internaute, les cartes dans un paquet, les figures d’un dessin). Dans de nombreux cas,
la taille de cette suite d’éléments va varier tout au long du programme. La liste des références des
produits commandés par un internaute augmentera à chaque produit commandé, et diminuera quand
l’utilisateur décidera de retirer un produit de sa commande.
Une première approche serait d’utiliser un tableau :
String [] commandeReferences;
C’est possible, mais pas très simple à mettre en ¡uvre : la taille d’un tableau ne peut plus varier une
fois qu’il a été créé. Pour commander un nouveau produit, il faudrait donc :
1. créer un nouveau tableau, plus grand ;
2. copier l’ancien tableau dans le nouveau ;
3. ajouter le nouvel élément ;
4. faire pointer la variable d’origine vers le nouveau tableau.
Soit en gros le code suivant :
String nouveauProduit= ......... ;
String [] tmp= new String [commandeReferences.length + 1];
for (int i= 0; i < commandeReferences.length; i++) {
tmp[i]= commandeReferences[i];
}
// ajout du nouveau produit dans la dernière case
tmp[tmp.length - 1]= nouveauProduit;
// la variable qui désigne la commande doit pointer sur le
// nouveau tableau :
commandeReferences= tmp;
Il faudrait, de la même manière, écrire le code nécessaire pour supprimer un élément, en rechercher
un, etc...
Comme ce type de problème est récurrent en informatique, java, comme la plupart des langages
de programmation, fournit les classes nécessaires dans ses bibliothèques standard. Nous allons étudier
la plus simple, la classe ArrayList.

12.10 Contenu de la classe ArrayList


La classe ArrayList permet donc de construire des tableaux de taille variable. De la même
manière qu’un tableau est un tableau d’int, de char, de String (etc.), une ArrayList contient des
valeurs d’un type donné. On doit préciser ce type quand on déclare la variable. Pour cela, on fait suivre
le nom de la classe ArrayList par le type des éléments, entre chevrons (< et >). Par exemple :
ArrayList<String> maListe;
déclare la variable maListe comme étant une référence vers une ArrayList de Strings.
Une ArrayList est un objet, et comme les tableaux, les objets sont créés par l’exécution de
l’instruction new. Nous verrons plus tard les détails. Pour l’instant, il suffit de comprendre que

8 NFA031 – CNAM 2017


CHAPITRE 12. UTILISER DES OBJETS 12.10. CONTENU DE LA CLASSE ARRAYLIST

maListe= new ArrayList<String>();


va créer une ArrayList vide.
Pour des raisons sur lesquelles nous reviendront plus tard, les ArrayList destinées à contenir
des entiers ou des nombres réels se déclarent respectivement comme ArrayList<Integer> et
ArrayList<Double> (notez les majuscules).
Enfin, notez que pour qu’une classe puisse utiliser les ArrayList, il faut écrire
import java.util.ArrayList;
avant la déclaration de votre classe dans le fichier java :
import java.util.ArrayList;
public class MaClasse {
...
}

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

NFA031 – CNAM 2017 9


12.10. CONTENU DE LA CLASSE ARRAYLIST CHAPITRE 12. UTILISER DES OBJETS

public static int somme(ArrayList<Integer> liste) {


int s=0;
for (int i= 0; i < liste.size(); i++) {
s= s + liste.get(i);
}
return s;
}
add(Type element) ajoute un élément à la fin de la liste. Pour construire la liste [2,3,5, 7, 11], on
écrira donc :
ArrayList<Integer> l= new ArrayList<Integer>();
l.add(2); l.add(3); l.add(5);
l.add(7); l.add(11);
Notez qu’en toute rigueur, add prend un argument du type précisé lors de la création de
l’ArrayList, c’est-à-dire Integer dans notre cas. Il faudrait donc écrire :
l.add(new Integer(2));
l.add(new Integer(3));
// etc...
Cependant, pour simplifier la vie des programmeurs, java 1.5 a introduit un système de
conversion automatique entre les types de base int, double, char, boolean et les
classes correspondantes (Integer, Double, Character et Boolean.
set(int i, Type element) remplace l’ancienne valeur qui était dans la case i par element. Logique-
ment, i doit être inférieure à la size() de l’ArrayList.
remove(int i) supprime l’élément qui est dans la case i ;
remove(Type element) supprime la première occurrence de l’élément de valeur element 2 ; si l’élé-
ment est présent plusieurs fois, il ne sera enlevé qu’une seule fois. Le contenu des cases est
décalé, et la longeur de l’ArrayList diminue de 1. Si l’élément n’est pas présent, la liste
n’est pas modifiée.

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.

10 NFA031 – CNAM 2017


CHAPITRE 12. UTILISER DES OBJETS 12.11. UN PETIT EXEMPLE

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.

12.11 Un petit exemple


À titre d’exemple voici un petit programme qui simule une caisse enregistreuse. On peut éditer le
ticket de caisse d’un client en y ajoutant des produits ou en les supprimant. Dans l’état actuel de nos
connaissances, nous avons représenté le ticket par deux ArrayList. La première contient les noms
des produits achetés, et la seconde contient leur prix.

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) {

NFA031 – CNAM 2017 11


12.11. UN PETIT EXEMPLE CHAPITRE 12. UTILISER DES OBJETS

// suppression du produit dans la case i.


afficherTicket(nomsArticles, prixArticles);
Terminal.ecrireString("numéro de l’article à enlever");
int i= Terminal.lireInt();
// on supprime le produit dans les deux ArrayList.
nomsArticles.remove(i);
prixArticles.remove(i);
Terminal.ecrireStringln("Ticket après suppression");
afficherTicket(nomsArticles, prixArticles);
} else {
fin = true;
}
}
Terminal.ecrireStringln("Ticket final");
afficherTicket(nomsArticles, prixArticles);
// calcul du prix
double total= 0;
for (int i= 0; i < prixArticles.size(); i++) {
total= total + prixArticles.get(i);
}
Terminal.ecrireStringln("Total " + total);
}

public static void afficherTicket(ArrayList<String> noms,


ArrayList<Double> prixArticles) {
for (int i= 0; i < noms.size(); i++) {
Terminal.ecrireStringln(i+ ". "+
noms.get(i)+
" prix: "+ prixArticles.get(i));
}
}
}

12 NFA031 – CNAM 2017


Exemple Analyse 1 : Opérations sur matières
et notes

V. Aponte

Cnam

7 décembre 2012
Sous-programmes : outil de découpage/structuration

Principe de conception/programmation : diviser pour régner

Objectif : Concevoir un programme comme un assemblage de


solutions à des sous-problèmes :

• Chaque sous-problème correspond à une partie (non triviale) du


problème global ;
• Concevoir un sous-programme par sous-problème ;
• Obtenir liste sous-programmes ⇒ noms + paramètres+ types+
description objectif (sans dire comment) ;

Guide : pouvons nous écrire une solution simple et courte par


asseblage d’appels aux sous-programmes ?
Analyse descendante

Processus de découpage d’un problème en sous-problèmes :


• en identifiant les sous-programmes à écrire pour obtenir la
solution ;
• chaque sous-programme utilise d’autres (éventuellement)
• chaque sous-programme reste très simple et court

Question à poser par sous-problème identifié :


• Que doit faire le sous-programme correspondant ?
• Son nom, ses paramètres, son résultat ?
• Sa signature Java ⇒ nom, type paramètres + résultat.
• Découpable en sous-sous-problèmes ? Si oui, continuer analyse
descendante sur les sous-sous-problèmes.
Analyse descendante : données

Répresentation des données :


• quelles sont les données d’entrée et/ou sortie ?
• comment les organiser ?
• quelles opérations aurons nous à leur appliquer ?
• coment les répresenter pour faciliter les opérations ?
• quelles hypothèses faisons nous sur les données ?

Lien entre sous-programmes et répresentation des données :


• chaque opération doit se traduire par une (des) méthode,
• doivent être utiles à notre problème,
• doivent maintenir les hypothèses sur les données.
Un exemple

Problème : gérer les notes d’un ensemble de matières, avec


opérations de calcul de moyenne, recherche d’une note, modification
d’une note, affichage du bulletin.

• 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

Bonjour. Voici les operations disponibles:


1. Afficher le bulletin
2. Chercher une note
3. Mofidier une note
4. Finir
Votre choix --->
Gestion des notes : les données
Répresentation des données : 2 tableaux

• tableau de chaînes avec noms des matières,


• tableau de notes (doubles)
• Hypothèses : tableaux de même taille, au moins 1 composante,
matière et sa note aux mêmes indices.

Opérations sur les données :


• afficher le contenu (matière + note) des 2 tableaux
• moyenne tableau notes ;
• chercher une note étant donné un nom de matière ;
• modifier une note étant donné un nom de matière ;
• intialiser les 2 tableaux ;
Sous-tâches du main

main

Initialisation Opérations

matières notes finir

menu+ afficher chercher modifier


choix bulletin note note
Sous-tâches d’initialisation des données

Initialisation

notes
matières

String [] double []
lireTab(int) lireTabNotes(String[])

lecture validée note

double
lireDoub(inf, sup)
Sous-tâches pour les opérations

Opérations
fin

void modifier
menu+ afficher opAfficheBulletin(mat,not)
bulletin note
choix

int double chercher


afficheMenuChoix() moyenne(not) note

lecture
validée entier void
opChercheNote(mat,not)
int
void
lireEnt(inf, sup)
opChangeNote(mat,not)

indice d'une int


matière
indiceMat(m, mat)
Liste détaillée de méthodes

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.

Intialiser les 2 tableaux


/* Lit un tableau de taille n */
static String [] lireTabString (int n)

/* Lit une note pour chaque matiere dans m */


static double [] lireTabNotes (String [] m)

Attention à lire un tableau avec au moins une matière ⇒ appel avec


paramètre n ≥ 1.
Liste de méthodes (2)

• afficher le contenu (matière + note) des 2 tableaux


/* Affiche matieres de m avec notes de t
* et leur moyenne
*/
static void opAfficheBulletin(double [] t,
String [] m)

⇒ a besoin de :
• calcul moyenne tableau notes :

static double moyenneTab (double [] t)


Préciser les hypothèses des méthodes

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

/*Retourne la moyenne d’un tableau t de double


* Hypothese: t est non vide
*/
static double moyenneTab (double [] t)
Liste de méthodes (3)

• chercher une note étant donné un nom de matière ;


/* Lit un nom de matiere et cherche son indice
* dans m. Si trouve, affiche sa note dans t,
* sinon signale une erreur
*/
static void opChercheNote(String [] m, double [] t)

⇒ a besoin de :
• chercher l’indice d’une chaîne dans un tableau de chaînes

/* Retourne l’indice d’une matiere si existe


* et -1 sinon.
**/
static int indiceMat(String a, String [] m)
Liste de méthodes (4)

• modifier une note étant donné un nom de matière ;


static void opChangeNote(String [] m, double [] t)

⇒ a (comme avant) besoin de :


• chercher l’indice d’une chaîne dans un tableau de chaînes

static int indiceMat(String a, String [] m)


Liste méthodes (5)

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()

• lecture validée d’une option du menu, d’une note, etc.


static int lireEnt(String me, int inf, int sup)
static double lireDou(String me,
double inf, double sup)
Pouvons nous écrire un main avec ces méthodes ?
int nb = lireEnt("Nombre de matieres? ",1,20);
String [] matieres = lireTabString(nb);
double [] notes = lireTabNotes(matieres);
boolean fin = false;
while (!fin) {
int choix = afficheMenuEtChoix();
if (choix == 1) {
opAfficheBulletin(notes, matieres);
} else if (choix == 2) {
opChercheNote(matieres, notes);
} else if (choix == 3) {
opChangeNote(matieres, notes);
} else if (choix == 4) {
Terminal.ecrireStringln(" *** Au revoir... ***
fin=true;
}
}
Bilan analyse, début du codage

Principe : l’analyse finit quand . . . commence le codage + tests.

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,

L’écriture du main permet de mettre à l’épreuve la pertinence


de notre analyse.
Codage et test

Par où commencer à coder ? :

• Principe : coder en premier les méthodes « feuilles », n’ayant


besoin d’autres méthodes ;
• méthodes d’affichage qui permettront de faire des tests ;
• laisser l’implantation de lecture validée pour plus tard, etc...
• chaque opération codée ⇒ testée minucieusement : permettra
de passer à autre chose sans avoir à y revenir.
Compilation et test

• Principe : déclarer toutes les méthodes, avec corps minimale


permettant de compiler :
/* Operation affichage du bulletin de notes */
static void opAfficheBulletin(double [] t,
String [] m) {
Terminal.ecrireStringln("non implante");
}

/* Retourne l’indice d’une matiere si elle existe


* et -1 sinon. **/
static int indiceMat(String a, String [] m) {
return -1;
}

• But : compiler tout le programme ; implantation incrémentale.


Codage méthodes initialisation

/* Lecture d’un tableau de String de taille n */


static String [] lireTabString (int n) {
String [] t = new String[n];
for (int i=0; i< t.length; i++) {
Terminal.ecrireString("Une matiere? ");
t[i] = Terminal.lireString();
}
return t;
}
Méthodes initialisation (2)

/* Lecture d’un tableau de notes par matiere */


static double [] lireTabNotes (String [] m) {
double [] t = new double[m.length];
for (int i=0; i< t.length; i++) {
t[i] = lireDou("Note de " + m[i]+" ? ",0,20);
}
return t;
}

⇒ a besoin de lireDou.
Méthode lecture double entre inf et sup

/* Lecture d’un double compris entre inf et sup


* avec message
*/
static double lireDou(String message,
double inf, double sup) {
while (true) {
Terminal.ecrireString(message);
double n = Terminal.lireDouble();
if (n<inf || n> sup) {
Terminal.ecrireStringln("Doit etre entre "+ inf+
} else return n;
}
}
Opérations sur le menu

/* Affichage menu et lecture choix operation.


* Retourne un choix valide d’operation
*/
static int afficheMenuEtChoix(){
Terminal.sautDeLigne();
Terminal.ecrireStringln("Bonjour. Operations disponib
Terminal.ecrireStringln(" 1. Afficher le bulletin");
Terminal.ecrireStringln(" 2. Chercher une note");
Terminal.ecrireStringln(" 3. Mofidier une note");
Terminal.ecrireStringln(" 4. Finir. ");
return(lireEnt("Votre choix ---> ", 1, 4));
}
Opération de recherche d’une note

/* Operation recherche d’une note */


static void opChercheNote(String [] m,
double [] t) {
Terminal.ecrireStringln("Nom de la matiere a chercher?
String nm = Terminal.lireString();
int ind = indiceMat(nm, m);
if (ind <0) {
Terminal.ecrireString("Matiere non trouvee.");
} else {
Terminal.ecrireString("Note de "+nm+" = "+t[ind]);
}
}

⇒ a besoin de indiceMat.
Indice d’une matière

/* Retourne l’indice d’une matiere si elle existe


* et -1 sinon.
**/
static int indiceMat(String a, String [] m) {
for (int i=0; i<m.length; i++){
if (a.equals(m[i])) return i;
}
return -1;
}
Fin du codage

Le reste du codage est laissé en exercice !

Vous aimerez peut-être aussi