Précédent Remonter Suivant

Chapitre 2  Les constructions de base en Java

... conduire par ordre mes pensées, en commençant par les objets les plus simples et les plus aisés à connaître, pour monter peu à peu, comme par degrés, jusques à la connaissance des plus composés ; et supposant même de l'ordre entre ceux qui ne se précèdent point naturellement les uns les autres. Descartes

2.1  Constantes et variables

La manière la plus simple de manipuler des valeurs dans un programme est d'écrire directement ces valeurs. Ainsi, on pourra écrire 24 pour désigner un nombre entier, 176.54 pour désigner le nombre réel correspondant, 2.54E+12 pour désigner le nombre 2.54 × 1012, 'a' pour indiquer un caractère, "Jean-Paul" pour désigner une chaîne de caractères, et true pour indiquer la valeur booléenne "vrai".

Toutes ces valeurs sont des constantes. Je peux par exemple demander à un programme d'afficher le résultat de l'opération 1+1. Le seul problème est bien entendu que si je souhaite effectuer une autre opération, il faudra modifier le programme, par exemple en écrivant 1+2. Or l'un des intérêts de la programmation est justement de pouvoir décrire une démarche opératoire qui reste la même alors que les données du problème peuvent changer. Par exemple, il est clair que l'opération "créditer un compte bancaire" correspond au même algorithme et au même programme, quels que soient le solde initial du compte et le montant dont on doit le créditer.

Il est donc indispensable de ne pas rester limité à l'emploi de constantes dans les programmes informatiques. D'un point de vue algorithmique, la variable est un objet qui a un nom (l'identificateur de la variable) et, au cours de sa vie, une valeur à chaque instant t donné. Concrètement, au sein de l'ordinateur, la variable va correspondre à un emplacement mémoire dans lequel on peut stocker une valeur. Quand on écrit un programme, le traducteur (compilateur ou interprète) associe à chaque variable l'adresse de l'emplacement mémoire correspondant et réalise l'adressage indirect nécessaire pour accéder en lecture ou en écriture à cet emplacement.

Il est bon de prendre l'habitude dès le début de donner à ses variables des noms mnémotechniques. Bien entendu, pour l'ordinateur, il est strictement équivalent que vous appeliez vos variables a, b, c, d, i1, i2, i3 ou soldeDuCompte, montantACrediter, ageDuCapitaine, nomDuClient ; mais vous comprenez aisément que lorsqu'un programme dépasse quelques dizaines de lignes, il est beaucoup plus facile pour un être humain de comprendre, de modifier et de maintenir un programme utilisant le deuxième type de noms de variables.

Revenons aux constantes. Nous avons vu qu'une valeur constante peut être écrite telle quelle dans un programme, par exemple 19.6 ou "Bonjour". Mais dans bien des cas, il est préférable de donner un nom symbolique aux constantes également, comme pour les variables. Pourquoi ? Prenons l'exemple d'un calcul de prix ttc. À l'heure où j'écris ces lignes, le taux de tva standard est de 19,6%. Si donc je fais tous mes calculs de prix ttc dans mon programme en multipliant les variables désignant le prix hors taxe par 1.196, je serai dans le pétrin le jour où le gouvernement relève ou baisse le taux de la tva. Il me faudra chercher toutes les occurrences du nombre 1.196 dans le programme, vérifier qu'elles correspondent bien à un calcul de tva, et faire les modifications. Il est donc bien plus judicieux de désigner la constante par un nom, comme pour les variables. C'est ce que permet le mot clé final, grâce auquel on écrira :
final double tauxTVA = 1.196;
pour indiquer que tauxTVA est une constante de type réel (cf. § 2.3). Tous les calculs se feront avec cette constante nommée, et quand le taux de tva changera, il y aura une seule ligne à modifier dans le programme... De même, en cas de programme multilingue, on pourra par exemple écrire :
final String salutation = "Bonjour";
ou au contraire
final String salutation = "Guten Tag";
ou
final String salutation = "Buenos dias";

2.2  Typage

Le type d'une expression ou d'une variable indique le domaine des valeurs qu'elle peut prendre et les opérations qu'on peut lui appliquer. En Java comme dans la plupart des langages évolués, toute variable doit être déclarée avec un type donné. Ce peut être un des types de base du langage, ou un type construit avec les outils de structuration fournis par le langage. Toute expression valide a aussi un type, et le résultat d'une opération doit a priori être affecté à une variable du type correspondant.

Il est relativement aisé de donner une interprétation ensembliste à la notion de type. Nous aurons l'occasion de le faire plus d'une fois, en particulier quand nous aborderons la notion de classe (cf. § 3.1 et 4.1). La plupart des langages évolués offrent les constructions nécessaires pour mettre en oeuvre les opérations ensemblistes de base que sont le produit de deux ensembles (et l'accès aux fonctions de projection associées), les fonctions et opérateurs, et la séquence ordonnée ou non d'éléments d'un ensemble.

2.3  Types élémentaires en Java

Le langage Java définit un certain nombre de types élémentaires ; les variables déclarées avec ces types correspondent des "cases mémoire" en adressage direct. La taille en mémoire de ces cases est définie par le type. Voici quelques exemples de déclarations et d'initialisations valides en Java :
boolean b = true;
int i = 3;
b = (i != 0);
char c = 'A';
char newline = '\n';
char apostrophe = '\'';
char delete = '\377';
char aleph = '\u05D0';
long somme = 456L;
float val = -87.56;
float cumul = 76.3f;
double pi = 3.14159;
double large = 456738.65D;
double veryLarge = 657E+234;
Attention : comme l'indiquent les exemples ci-dessus, en Java, toute variable doit être déclarée (avec indication de son type) une et une seule fois, avant d'être utilisée.
Exercice 1   Quelles sont les erreurs de syntaxe dans les lignes qui suivent ?

        boolean b;
        double a2 = 5.2;
        int k = 5;
        int j = k;
        int i = 7;
        char a = i;
        int k = 7;
        int l = t;
        int t = 7;
  

2.4  Expressions

Java offre un jeu riche d'opérateurs pour écrire toutes sortes d'expressions arithmétiques et logiques. Des règles de priorité entre opérateurs permettent d'écrire les expressions de la manière la plus "naturelle" possible ; par exemple, a + b * c équivaut à a + (b * c) et non à (a + b) * c. Ceci étant, en cas de doute, rien ne vous empêche de parenthéser vos expressions.

2.4.1  Opérateurs arithmétiques

Les opérateurs arithmétiques unaires sont + et - ; par exemple, -a équivaut à -1 * a. Les opérateurs arithmétiques binaires sont présentés dans le tableau ci-après.
Op. Exemple Description / remarques
+ a + 5 addition -- ou concaténation de chaînes dans le cas particulier de String (cf. § 4.4)
- b - c soustraction
* 13 * x multiplication
/ a / b division -- attention, si les opérandes sont entiers, il s'agit de la division euclidienne ! Ceci est une source connue d'erreurs chez les débutants.
% 12 % 5 modulo (reste de la division euclidienne)

Exercice 2   Donner la valeur des variables après les instructions suivantes :

    int i = 13;
    int j = 5;
    i = i % 5;
    j = j * i + 2;
    int k = (i * 7 + 3) / 5;
  

2.4.2  Opérateurs logiques et relationnels

Tout d'abord, si vous n'avez jamais vu l'algèbre de Boole dans vos études, je vous conseille de lire au préalable l'annexe B, qui vous donne une courte introduction à la notion même d'opérations et de fonctions logiques.

Un opérateur relationnel compare deux valeurs. Les opérateurs logiques permettent d'opérer sur des valeurs logiques, habituellement obtenues grâce à l'application d'opérateurs relationnels.

Les opérateurs relationnels de Java sont les suivants :
Op. Exemple Description / remarques
> a > 0 a > 0
>= b >= c b ² c
< a < 0 a < 0
<= a <= b a £ b
== a == b test d'égalité a=b -- attention, ne pas confondre avec l'affectation d'une valeur à une variable (cf. § 2.5), c'est une source d'erreur fréquente, qui peut avoir des conséquences catastrophiques !
!= a != b test d'inégalité a ¹ b
instanceof a instanceof C test d'appartenance à une classe (cf. p. ??).

Les opérateurs logiques disponibles sont les suivants :
Op. Exemple Description / remarques
&& (a>0) && (b<0) et logique -- le deuxième opérande n'est évalué que si le premier opérande est vrai
|| (a>0) || (b<0) ou logique -- le deuxième opérande n'est évalué que si le premier opérande est faux
! !(b >= c) non logique
& (a>0) & (b<0) et logique -- les deux opérandes sont toujours évalués -- Utilisation déconseillée
| (a>0) | (b<0) ou logique -- les deux opérandes sont toujours évalués -- Utilisation déconseillée

Je vous conseille de prendre l'habitude d'utiliser par défaut les versions && et || des opérateurs et/ou.

Java offre également un opérateur à trois opérandes, qui permet de construire des expressions conditionnelles du type c ? a : b ; celle-ci donne comme résultat le second opérande (a) si le premier opérande (c) est vrai, et le troisième opérande (b) dans le cas contraire. Ainsi, (x > y) ? x : y est une expression qui a pour valeur max(x,y).

2.4.3  Opérateurs bit à bit

Ce sont des opérateurs qui permettent d'effectuer des opérations au niveau des bits. Bien que moins fréquemment utilisés, nous les mentionnons pour mémoire ; mais ne les utiliserons pas dans ce cours.
Op. Exemple Description / remarques
>> 17 >> 3 décalage de 3 bits sur la droite -- ici le résultat est 2
>>> a >>> 5 toujours un décalage de bits sur la droite (ici 5 bits), mais en version non signée
<< 0x03 << 2 décalage de 2 bits sur la gauche -- je profite de cet exemple pour introduire la notation hexadécimale des nombres (10 s'écrit A, 11 s'écrit B, ... 15 s'écrit F) ; le résultat de l'exemple est 0x0C
& 0x75 & 0xF0 et bit à bit -- le résultat de l'exemple donné est 0x70
| 0x75 | 0xF0 ou bit à bit -- le résultat de l'exemple donné est 0xF5
^ 0x75 ^ 0xF0 ou exclusif (xor) bit à bit -- le résultat de l'exemple donné est 0x85
~ ~0xF0 complément bit à bit -- le résultat de l'exemple donné est 0x0F

2.5  L'affectation

Nous avons vu (cf. § 2.1) qu'à une variable correspond un emplacement mémoire. Pour simplifier les explications qui vont suivre, considérons la mémoire comme une table M ; nous supposerons que la variable de nom x est stockée à la "case" M[i]. On appelle l-valeur (l-value, c'est-à-dire left-value) de la variable x son adresse en mémoire, dans notre cas i. On appelle r-valeur (r-value, c'est-à-dire right-value) de x la valeur stockée à cet emplacement, dans notre cas M[i].

L'affectation est une opération fondamentale dans les langages de programmation impératifs, dont Java fait partie ; elle consiste à changer la r-valeur d'une variable, c'est-à-dire à stocker une nouvelle valeur à l'emplacement désigné par la l-valeur de la variable. De manière conceptuelle, on la note habituellement x ¬ y, qui signifie qu'on stocke la r-valeur de y à l'adresse désignée par la l-valeur de x. En Java, cette opération s'écrit x = y.

Attention -- on ne pose pas ici une égalité, et on n'établit pas de lien pérenne entre les deux variables x et y. On se contente, à un instant précis, de recopier la valeur stockée dans y à l'adresse de x. Ce qui suit illustre ce point important :
  int x = 3;
  int y = 5;             // x == 3, y == 5
  x = y;                 // x == 5, y == 5
  y = 6;                 // x == 5, y == 6 : x et y ne sont pas des variables liées !
Il est très fréquent d'ajouter ou de soustraire une valeur à une variable, par exemple d'écrire a = a + 3;2. Java vous offre donc la possibilité d'écrire la même chose de manière raccourcie ; de même pour les autres opérations arithmétiques, ainsi que les opérations bit à bit :
  a += 3;                 // équivaut à a = a + 3
  a -= 3;                 // équivaut à a = a - 3
  a *= 3;                 // équivaut à a = a * 3
  a /= 3;                 // équivaut à a = a / 3
  a %= 3;                 // équivaut à a = a % 3
  a <<= 3;                 
  // etc. pour >>=, >>>=, &=, |= et ^=
Parmi ces opérations, le cas particulier de l'incrémentation et de la décrémentation est assez fréquent pour justifier d'une notation particulière :
  a++;                 // équivaut à a += 1 ou à a = a +1
  ++a;                 // équivaut à a++, sauf pour la valeur rendue
  a--;                 // équivaut à a -= 1 ou à a = a -1
  --a;                 // équivaut à a--, sauf pour la valeur rendue
Cette question de valeur rendue vous semble probablement étrange ; aussi longtemps que vous utilisez ces opérations de la manière dont elles sont illustrées ci-dessus, vous n'avez pas besoin de vous tracasser à ce sujet. C'est lorsqu'on commence à utiliser la r-valeur de l'expression -- ce qui est appelé par certains "programmer par effets de bord" et que je vous déconseille vivement, du moins dans un premier temps -- qu'il faut y être attentif, dans la mesure où la forme préfixée rend la valeur de la variable après incrémentation/décrémentation, alors que la forme postfixée rend la valeur avant l'opération :
  int x = 3;
  int y = 5;           // x == 3, y == 5
  x = y++;             // x == 5, y == 6
  x = ++y;             // x == 7, y == 7

2.6  Mon premier programme Java

À ce stade, nous disposons de constantes, de variables, d'expressions et de l'instruction d'affectation d'une valeur à une variable. On peut déjà écrire des programmes simples avec ces éléments. Nous allons donc construire notre tout premier programme en Java. Nous nous rendrons vite compte qu'il faut recourir à plusieurs constructions que nous n'avons pas encore expliquées ; prenez-les dans un premier temps comme des "recettes", et nous indiquerons chaque fois la référence en avant de la partie du cours où la recette est expliquée.

Quand nous commençons ainsi à écrire des programmes, il est important de prendre tout de suite de bonnes habitudes d'écriture ; je vous invite donc dès maintenant à consulter l'annexe H.

Comme thème récurrent dans ce cours, nous allons choisir la gestion d'un compte bancaire. Commençons par écrire les lignes nécessaires pour créditer un compte bancaire d'un montant donné :
  int solde = 0;     // variable désignant le solde et initialisée à 0
  int montant = 16;  // variable désignant le montant à créditer

  solde += montant;  // l'opération de crédit à proprement parler
Pour que ces lignes constituent un programme qui puisse s'exécuter, il faut les inclure dans une construction qui vous semblera probablement compliquée pour l'instant, mais que nous expliquerons par la suite. Tout d'abord, l'ensemble de lignes doit être inclus dans une procédure particulière appelée main (cf. § 3.2.4), dont la syntaxe est assez rébarbative au premier abord (mais nous aurons l'occasion d'expliquer les raisons de cette syntaxe -- cf. § 4.3.1). Mais Java impose aussi que toute procédure doit appartenir à une classe, car on ne peut lancer une exécution Java qu'en précisant dans quelle classe se trouve le code à exécuter (cf. § 4.1). Nous définirons donc une classe Banque dans le fichier Banque.java (cf. § H.1).

Nous avons maintenant un programme, contenu dans la classe Banque, et qui est exécuté lorsque l'interprète Java est lancé sur cette classe. Le contenu du fichier Banque.java est donc :
public class Banque {
    public static void main(String[] args) {
        int solde = 0;
        int montant = 16;

        solde += montant;
    }
}
Nous avons maintenant un programme qui compile et qui s'exécute ; malheureusement, il ne communique pas avec le monde extérieur. On aimerait au minimum qu'il affiche le résultat de son calcul. Nous allons pour cela recourir à une fonction de la bibliothèque d'entrées/sorties fournie avec Java, appelée java.io (cf. § 6.3) ; cette fonction permet d'afficher à l'écran une chaîne de caractères, et convertit en chaîne de caractères tous les objets d'un autre type, tel que int dans notre exemple. Nous allons également utiliser une propriété de la classe String, représentant les chaînes de caractères (cf. § 4.4), à savoir que l'opérateur + permet de concaténer deux chaînes de caractères.

Le programme ainsi modifié est donné ci-après :
// Classe Banque - version 1.0
import java.io.*;       // Importer toutes les fonctionnalités de java.io

public class Banque {
    public static void main(String[] args) {
        int solde = 0;
        System.out.println("Le solde initial est de " + solde + " euros.");
        int montant = 16;

        solde += montant;
        System.out.println("Le nouveau solde est de " + solde + " euros.");
    }
}
Quand on l'exécute, un résultat s'affiche :
  Le solde initial est de 0 euros.
  Le nouveau solde est de 16 euros.
Allons maintenant un peu plus loin : une grosse faiblesse du programme reste que le montant à créditer est donné dans le programme lui-même, alors qu'on aimerait qu'il soit donné par l'utilisateur. Pour cela, nous allons recourir à une nouvelle fonction de la bibliothèque d'entrées/sorties ; malheureusement, pour l'appeler, nous avons d'abord besoin de recourir à une petite gymnastique mystérieuse de conversions. N'ayez crainte ; nous comprendrons plus tard les raisons de ces conversions (cf. § 6.3.1). Quand on lit quelque chose au clavier, il peut y avoir des problèmes (l'utilisateur peut par exemple donner une entrée invalide) et le système le sait ; il faut donc lui dire comment traiter ces éventuels problèmes, appelés exceptions (cf. § 6.4). Enfin, la fonction de lecture rend une chaîne de caractères, qu'il conviendra de convertir en un entier, par une fonction de conversion ad hoc. La nouvelle version du programme est donnée ci-après ; dans tout ce polycopié, nous indiquons sur fond grisé les lignes qui changent d'une version à l'autre, sauf quand les modifications sont majeures et nécessitent de reconsidérer l'ensemble des lignes :
// Classe Banque - version 1.1
import java.io.*;       // Importer toutes les fonctionnalités de java.io

public class Banque {
    public static void main(String[] args) {
        // Conversions mystérieuses...
        InputStreamReader ir = new InputStreamReader(System.in);
        BufferedReader br = new BufferedReader(ir);

        int solde = 0;
        System.out.println("Le solde initial est de " + solde + " euros.");
        
        System.out.print("Montant à créditer = ");
        System.out.flush();
        String lecture = "";      // la chaîne qu'on va lire, initialisée à rien
        try { // prise en compte des exceptions par try/catch
            lecture = br.readLine();
        } catch (IOException ioe) {}

        int montant = Integer.parseInt(lecture);  // conversion en int de la chaîne lue

        solde += montant;       // on retrouve l'incrémentation classique
        System.out.println("Le nouveau solde est de " + solde + " euros.");
    }
}
Le résultat de l'exécution donne (les données entrées par l'utilisateur sont toujours indiquées sur fond grisé) :
Le solde initial est de 0 euros.
Montant à créditer = 43
Le nouveau solde est de 43 euros.
Voilà ! nous avons un premier programme, certes rudimentaire, mais qui fait quelque chose d'à peu près sensé... Bien évidemment, nous avons dû passer sous silence les raisons de beaucoup de détails, qui vous paraissent peut-être pour l'instant relever plus de la formule magique que de la démarche raisonnée. Mais les choses vont se clarifier au fur et à mesure de la progression.

2.7  De la structuration du discours : les instructions de contrôle

Pour structurer et décomposer un programme, l'idée de base dans beaucoup de langages est que stricto sensu, on ne gère toujours qu'une seule instruction à chaque niveau, y compris le programme complet. Cette instruction peut être :

2.7.1  Instructions conditionnelles

Dans le modèle classique en vigueur depuis la machine de von Neumann (cf. chapitre A), le flot normal d'exécution est séquentiel, c'est-à-dire qu'après exécution d'une instruction élémentaire, c'est l'instruction immédiatement consécutive dans le programme qui est activée. Les langages de programmation de haut niveau ont pour la plupart repris ce principe simple, et c'est le cas notamment de Java : des instructions atomiques vont être exécutées dans l'ordre dans lequel elles se succèdent dans le programme.

Cependant, pour élaborer des programmes plus complets, il est nécessaire de disposer d'instructions permettant de modifier dans certains cas ce déroulement linéaire. La première que nous allons voir est la conditionnelle. Pour oser une comparaison ferroviaire, on peut dire qu'il existe deux types de conditionnelles : L'aiguillage correspond à la structure algorithmique classique appelée si--alors--sinon, et qu'on peut écrire sous la forme suivante :
si condition
alors instruction1
[sinon instruction2]
finsi

La partie sinon est facultative. Pour l'indiquer, nous avons mis des crochets [ ] ci-dessus. La construction correspondante en Java utilise les mots clés if et else. Notons qu'il n'y a pas de mot clé particulier pour traduire alors.

Illustrons tout de suite cette construction en modifiant notre programme bancaire. Nous souhaitons maintenant pouvoir choisir d'effectuer une opération de crédit ou de débit. Il faut donc demander à l'utilisateur ce qu'il souhaite faire, et effectuer en conséquence les opérations appropriées. Dans le programme, nous utilisons aussi, sans l'expliquer pour l'instant5, la construction choix.equals("D"), qui permet de comparer deux chaînes de caractères. L'interface reste ici rudimentaire, on pourrait entre autres imaginer que si l'utilisateur donne un choix invalide, on lui repose la question au lieu de décider par défaut que c'est une opération de crédit :
// Classe Banque - version 1.2
import java.io.*;       // Importer toutes les fonctionnalités de java.io

public class Banque {
    public static void main(String[] args) {
        InputStreamReader ir = new InputStreamReader(System.in);
        BufferedReader br = new BufferedReader(ir);

        int solde = 0;
        System.out.println("Le solde initial est de " + solde + " euros.");
        
        System.out.print("[D]ébit ou [C]rédit (choix par défaut) ? ");
        String choix = "";
        try {
            choix = br.readLine();
        } catch (IOException ioe) {}

        boolean credit = true;   // variable vraie si c'est un crédit (défaut)

        if (choix.equals("D") || choix.equals("d")) {
            credit = false;
        }
        // sinon on reste à true par défaut, donc crédit

        // Notez l'utilisation de l'opérateur à 3 opérandes ?:
        System.out.print("Montant à " + (credit ? "créditer" : "débiter") + " = ");
            
        System.out.flush();
        String lecture = "";      // la chaîne qu'on va lire, initialisée à rien
        try {
            lecture = br.readLine();
        } catch (IOException ioe) {}

        int montant = Integer.parseInt(lecture);  // conversion en int de la chaîne lue

        if (credit) {
            solde += montant;
        }
        else {
            solde -= montant;
        }
        System.out.println("Le nouveau solde est de " + solde + " euros.");
    }
}
Et voici le résultat de deux exécutions successives de ce programme :
Le solde initial est de 0 euros.
[D]ébit ou [C]rédit (choix par défaut) ? d
Montant à débiter = 124
Le nouveau solde est de -124 euros.

Le solde initial est de 0 euros.
[D]ébit ou [C]rédit (choix par défaut) ? c
Montant à créditer = 654
Le nouveau solde est de 654 euros.
Vous avez peut-être noté l'emploi de blocs composés, délimités par `{' et `}', bien que ceux-ci ne contiennent qu'une seule instruction. Stricto sensu, ils ne sont pas nécessaires ici, mais il est fortement conseillé d'en systématiser l'usage pour toutes les constructions conditionnelles ou itératives ; cela augmente la lisibilité et la clarté du programme, en mettant mieux en valeur la structure de votre "discours".

La deuxième construction conditionnelle possible correspond à ce que nous avons appelé la gare de triage. Pour l'exprimer dans un algorithme, on écrira quelque chose du genre :
ì
ï
í
ï
î
cas1 ® instruction1
cas2 ® instruction2
...
casn ® instructionn

En Java, il n'existe pas de vraie construction syntaxique permettant de traduire cette notion ; on utilise habituellement une série de if ...else imbriqués. Cependant, dans le cas particulier où les différents cas peuvent s'exprimer sous la forme de valeurs possibles pour une expression de type entier (byte, short, int ou long) ou caractère (char), on dispose de la construction switch. Cependant, comme nous l'illustrons dans l'exemple ci-après, cette construction correspond plutôt à un ensemble de branchements à des points précis du programme, ce qui explique la présence des instructions break ; sans celles-ci, le flot de contrôle se poursuivrait séquentiellement. Cela explique aussi l'absence de structuration par des blocs composés : on reste au même niveau de structuration. À noter aussi qu'on ne peut pas, dans une construction de type switch, employer des conditions booléennes, les étiquettes de branchement -- introduites par le mot clé case, l'étiquette default indiquant une valeur par défaut -- correspondant à des valeurs constantes et discrètes, de type caractère ou entier. Il faut donc réserver cette construction à des cas très précis et spécifiques, et privilégier une suite de constructions if ...else, éventuellement imbriquées, dans la majorité des cas de conditionnelles.

Donnons toutefois une nouvelle variante du programme bancaire où nous utilisons une condition switch pour choisir le type d'opération à effectuer. Cette fois-ci, au lieu de comparer des chaînes de caractères, nous utilisons l'opération choix.charAt(0) pour récupérer le premier caractère (position 0) de la chaîne6, le switch se faisant sur la valeur de ce caractère (majuscules et minuscules autorisées) :
// Classe Banque - version 1.3
import java.io.*;       // Importer toutes les fonctionnalités de java.io

public class Banque {
    public static void main(String[] args) {
        InputStreamReader ir = new InputStreamReader(System.in);
        BufferedReader br = new BufferedReader(ir);

        int solde = 0;
        System.out.println("Le solde initial est de " + solde + " euros.");
        
        System.out.print("Votre choix : [D]ébit, [C]rédit, [F]in ? ");
        String choix = "";
        try {
            choix = br.readLine();
        } catch (IOException ioe) {}
        boolean fin = false;     // variable vraie si on veut s'arrêter
        boolean credit = false;   // variable vraie si c'est un crédit

        // Récupérer la première lettre de la chaîne saisie
        char monChoix = choix.charAt(0);
        switch(monChoix) {
        case 'C':
        case 'c':       // Même chose pour majuscule et minuscule
            credit = true; 
            fin = false;
            break;     // Pour ne pas continuer en séquence
        case 'd':
        case 'D':
            credit = false;
            fin = false;
            break;
        case 'f':
        case 'F':
            fin = true;
            break;
        default:      // Etiquette spéciale si aucun des cas prévus n'est vérifié
            fin = true;  // On va considérer que par défaut on s'arrête
        }

        // Poser la question uniquement si on n'a pas choisi de finir
        // Notez que toute cette partie reste identique au programme précédent
        // mais elle est maintenant incluse dans une construction if
        if (!fin) {
            System.out.print("Montant à " + (credit ? "créditer" : "débiter") + " = ");
            
            System.out.flush();
            String lecture = "";      // la chaîne qu'on va lire, initialisée à rien
            try {
                lecture = br.readLine();
            } catch (IOException ioe) {}

            int montant = Integer.parseInt(lecture);  // conversion en int

            if (credit) {
                solde += montant;
            }
            else {
                solde -= montant;
            }
        }

        // Dans tous les cas, imprimer le nouveau solde
        System.out.println("Le nouveau solde est de " + solde + " euros.");
    }
}
Un exemple de deux exécutions :
Le solde initial est de 0 euros.
Votre choix : [D]ébit, [C]rédit, [F]in ? d
Montant à débiter = 123
Le nouveau solde est de -123 euros.
  
Le solde initial est de 0 euros.
Votre choix : [D]ébit, [C]rédit, [F]in ? f
Le nouveau solde est de 0 euros.

2.7.2  Instructions itératives

Pour garder l'analogie ferroviaire, la construction itérative permet de réaliser un circuit dans le flot de contrôle -- ce qui plaira sûrement à tous les amateurs de modélisme. Plus sérieusement, une construction itérative permet de répéter les mêmes instructions Java offre deux constructions correspondant à ces deux cas de figure. Commençons par la première. En Java, elle s'écrit avec les mots clés while et éventuellement do. En reprenenant l'exemple bancaire, ajoutons maintenant la possibilité d'effectuer successivement plusieurs opérations de débit et de crédit, jusqu'à ce que l'utilisateur désire finir :
// Classe Banque - version 1.4
import java.io.*;       // Importer toutes les fonctionnalités de java.io

public class Banque {
    public static void main(String[] args) {
        InputStreamReader ir = new InputStreamReader(System.in);
        BufferedReader br = new BufferedReader(ir);

        int solde = 0;
        System.out.println("Le solde initial est de " + solde + " euros.");
        
        boolean fin = false;     // variable vraie si on veut s'arrêter
        while (!fin) {  // tant qu'on n'a pas demandé de finir
            System.out.print("Votre choix : [D]ébit, [C]rédit, [F]in ? ");
            String choix = "";
            try {
                choix = br.readLine();
            } catch (IOException ioe) {}

            boolean credit = false;   // variable vraie si c'est un crédit

            // Récupérer la première lettre de la chaîne saisie
            char monChoix = choix.charAt(0);
            switch(monChoix) {
            case 'C':
            case 'c':       // Même chose pour majuscule et minuscule
                credit = true; 
                fin = false;
                break;     // Pour ne pas continuer en séquence
            case 'd':
            case 'D':
                credit = false;
                fin = false;
                break;
            case 'f':
            case 'F':
                fin = true;
                break;
            default:
                fin = true;  // On va considérer que par défaut on s'arrête
            }

            if (!fin) {
                System.out.print("Montant à " + 
                                 (credit ? "créditer" : "débiter") + 
                                 " = ");
                System.out.flush();
                String lecture = "";
                try {
                    lecture = br.readLine();
                } catch (IOException ioe) {}

                int montant = Integer.parseInt(lecture);  // conversion en int

                if (credit) {
                    solde += montant;
                }
                else {
                    solde -= montant;
                }
                // N'afficher le nouveau solde que s'il y a eu une opération
                System.out.println("Le nouveau solde est de " + solde + " euros.");
            }
        }
    }
}
Un exemple d'exécution est donné ci-après :
Le solde initial est de 0 euros.
Votre choix : [D]ébit, [C]rédit, [F]in ? C
Montant à créditer = 500
Le nouveau solde est de 500 euros.
Votre choix : [D]ébit, [C]rédit, [F]in ? D
Montant à débiter = 234
Le nouveau solde est de 266 euros.
Votre choix : [D]ébit, [C]rédit, [F]in ? D
Montant à débiter = 65
Le nouveau solde est de 201 euros.
Votre choix : [D]ébit, [C]rédit, [F]in ? C
Montant à créditer = 67
Le nouveau solde est de 268 euros.
Votre choix : [D]ébit, [C]rédit, [F]in ? F
La variante do ...while a comme seule différence que les instructions sont exécutées une première fois avant que soit testée la condition d'arrêt. Nous aurions par exemple pu écrire une version légèrement différente du programme précédent, avec :
        do {
            System.out.print("Votre choix : [D]ébit, [C]rédit, [F]in ? ");
            ... // etc.
        } while (!fin);
Dans le cas présent, la différence entre ces deux versions est minime ; il y a cependant des cas où le choix de l'une ou de l'autre des variantes rend l'écriture du programme plus simple ou plus claire.

Pour le deuxième type d'itération, qui permet de parcourir tous les éléments d'un ensemble ou d'une collection, il est souvent utile d'employer une autre construction Java, celle qui emploie le mot clé for. Si par exemple, au lieu de demander à l'utilisateur d'indiquer quand il a fini de saisir des écritures comptables, on sait qu'on va lui en demander dix, on pourra écrire :
  for (int i = 0 ; i < 10 ;  i++) {
    System.out.print("Votre choix : [D]ébit ou [C]rédit ? ");
    ... // etc
  }
Les trois expressions séparées par des `;' dans cette construction for correspondent respectivement à l'initialisation de la boucle, à la condition de continuation, et au passage à l'élément suivant de l'ensemble à traiter. Les plus perspicaces auront probablement compris qu'une construction équivalente7, dans le cas présent, serait :
  int i = 0;
  while (i < 10) {
    System.out.print("Votre choix : [D]ébit ou [C]rédit ? ");
    ... // etc
    i++;
  }
NB : Une erreur assez commune faite par les débutants et de séparer les trois expressions constitutives de la construction for par des virgules et non par des points-virgules. Tenez-le vous pour dit !

Le principal avantage de for est de rendre explicite l'énumération des éléments d'un ensemble ou d'une collection. Nous aurons l'occasion d'illustrer plus complètement l'utilisation de cette construction dans d'autres paragraphes (cf. § 3.3).

Deux mots clés supplémentaires peuvent être utilisés dans les itérations8 :
1
Stricto sensu, les types Java entiers et réels représentent bien entendu des sous-ensembles de Z et de R, du fait de la représentation discrète et bornée des nombres en informatique (cf. § F.2 et F.3).
2
Cet exemple illustre bien la différence fondamentale entre une égalité mathématique -- qui n'aurait aucun sens ici -- et l'affectation : ici, on prend la r-valeur de a, on lui ajoute 3, et on stocke le résultat à l'adresse donnée par la l-valeur de cette même variable a.
3
En règle générale, la déclaration d'une variable doit être immédiatement suivie de l'initialisation de cette variable. Sauf cas très particulier, quand vous n'avez aucun moyen de donner une valeur d'initialisation réaliste à une variable, nous vous conseillons d'adopter ce principe.
4
Pour ceux qui connaissent le langage Pascal, il faut noter qu'en Pascal, `;' est un séparateur d'instructions, ce qui implique entre autres que la dernière instruction d'un bloc n'est pas terminée par un `;' -- en Java, en revanche, chaque instruction se termine par un `;'
5
Pour ceux qui sont curieux de savoir comment ça marche, voir § 4.4.
6
Une fois de plus, les esprits curieux peuvent faire un saut en avant jusqu'au § 4.4 pour mieux comprendre cette opération.
7
Au petit détail près que dans un cas la variable i n'existe que lors de l'itération, alors que dans l'autre elle est déclarée avant le début de la boucle et continue donc sa vie à la sortie de l'itération.
8
On peut noter que ces deux instructions break et continue peuvent être suivies d'une étiquette, qui spécifie soit une boucle englobante (pour continue), soit une instruction quelconque englobante (pour break). Dans ce dernier cas, break indique que l'on veut quitter le niveau englobant ainsi spécifié. Nous ne vous conseillons pas d'utiliser ces fonctionnalités plus avancées...

Précédent Remonter Suivant