Les bases de la programmation JAVA

Ce chapitre va s'articuler selon deux grands points :

  1. La description des types de données fondamentaux de Java
  2. Les structures de programmation classiques (choix et boucles)

Les types de données fondamentaux

A l'instar de la plupart des langages de programmation, Java propose des types de données fondamentaux pour manipuler, en particulier, les nombres entiers, les nombres réels ainsi que les booléens et les caractères.

Il est tentant de leur associer la classe String qui remplace en JAVA (et de façon très avantageuse) le fameux char* du C pour la représentation des chaînes de caractères. Malgré son statut de classe à part entière, et en raison de l'importance cruciale des chaînes de caractères, String présente également certaines caractéristiques empruntées aux types de données fondamentaux qui en font vraiment un type à part.

Aspect particulier du langage Java : chacun de ses types fondamentaux est associée à une classe dite wrapper dont nous détaillerons l'usage.

Les types numériques

Les types entiers

Contrairement à un langage comme le C++ qui ne spécifie officiellement que les caractéristiques des types réels, tous les types atomiques sont complètement caractérisés par la norme du JAVA. Le but est bien d'entendu d'assurer la portabilité du code. Il en résulte également un grand avantage pour le programmeur qui ne se pose jamais la question de savoir si la machine sur laquelle va s'exécuter son application dispose du même type entier que sa machine de développement.

Autre fait marquant, tous les nombres entiers sont signés. Le mot clef unsigned n'existe pas. De même, long n'est pas un modificateur en Java mais bel et bien un type. Le tableau suivant récapitule la liste des types entiers disponibles ainsi que leur taille. Rappelons que pour un entier signé codé sur n bits, la portée va de -2n-1 à 2n-1-1.

Type Taille mémoire
byte 1 octet
short 2 octets
int 4 octets
long 8 octets

Tableau %%CHAPITRE%%.%%TABLEAU%% Les types entiers en JAVA

Lors d'une opération arithmétique entre types entiers, les conversions se font toujours du type le moins étendu vers le type le plus étendu. Une constante « en dur » est toujours considérée comme appartenant au type int. Vous pouvez néanmoins la forcer en type long en lui ajoutant le suffixe L, par exemple : 1234L ; finalement vous avez la possibilité de spécifier un nombre en héxadécimal en utilisant le préfixe 0x à l'instar ce ce que l'on fait en C. Par exemple 0x20 pour 32.

Les types réels

Le JAVA reconnaît 2 types entiers réels normalisés IEEE-754 similaires à leurs homonymes du langage C. Leurs caractéristiques sont les suivantes :

Type Taille mémoire Exposants extrèmes Chiffres significatifs
float 4 octets 10±38 6
double 8 octets 10±308 15

Tableau %%CHAPITRE%%.%%TABLEAU%% Les types réels en JAVA

Contrairement au langage C, il existe une véritable arithmétique en nombres réels à simple précision (float). Une expression ne mettant en jeu que des float s'évaluera en arithmétique à simple précision sans passer par la double précision comme en C. En revanche, si floats et doubles sont mélangés dans une même expression, celle-ci s'effectuera en double précision avec, éventuellement, troncature si le résultat doit être affecté à un float.

A l'instar des constantes entières qui peuvent être forcées au type long à l'aide du suffixe L, il est possible d'indiquer qu'une constante réelle est de type float en lui adjoignant la lettre F. En l'absence de ce dernier, toute constante réelle est considérée comme étant du type double. Le suffixe optionnel D peut néanmoins être ajouté afin de bien marquer l'appartenance au type à double précision.

Interaction entre réels et entiers

Lorsque des nombres entiers et réels sont mis aux prises dans une même expression, il y a promotion arithmétique vers le type le plus fort. En revanche, si seulement des nombres entiers sont mis en jeu, l'opération aura toujours lieu sur les entiers. Par exemple :

int    i = 2;
int    j = 3;
int    k = i / j; // a pour résultat 0 
                  // L'opération est évaluée sur les entiers et affectée à un nombre entier
double x = i / j; // a pour résultat 0.0
                  // L'opération est évaluée sur les entiers et affectée à un nombre réel
double y = (double)i / j; // a pour résultat 0.66666666666
                          // i est converti en double, puis j est converti en double
                          // l'opération est alors effectuée en réel à double précision

Exemple %%CHAPITRE%%.%%Exemple%% Arithmétique entière

Les classes wrappers des types numériques

Dans certains cas, il est utile que les types numériques soient des objets. C'est en particulier le cas lorsque l'on souhaite les insérer à l'intérieur d'une collection de l'API Java qui n'accepte que des objets. Ainsi, les classes Integer, Short, Long, Float et Double sont elles respectivement associées aux types élémentaires integer, short, long, float et double. Il est à noter que toutes ces classes dérivent de la classe abstraite Number.

Chaque classe dispose de méthodes de conversion depuis et vers la classe String associée aux chaînes de caractères mais également de méthodes permettant de passer facilement de la forme Objet à la forme fondamentale ainsi que de constantes pour les valeurs extrèmes. Le petit exemple suivant illustre certaines de ces fonctionnalités.

public class EssaiWrappers
{
  static public void main(String args[])
  {
    int     unEntier = 10;
    float   unFloat  = 10.5f;
    double  unDouble = 3.14d;

    Double  wrDouble  = new Double(unDouble);
    Float   wrFloat   = new Float(unFloat);
    Integer wrInteger = new Integer(unEntier);
    
    Double  wrDouble2=Double.valueOf("56.72");      
            // valueOf : String -> Objet
    double  unDouble2=Double.parseDouble("57.89");  
            // parseXXX : String -> type elementaire XXX

    System.out.println("Plus petit nombre double " + Double.MIN_VALUE);
    System.out.println("Plus grand nombre double " + Double.MAX_VALUE);

    System.out.println("Moins l'infini           " + Double.NEGATIVE_INFINITY);
    System.out.println("Plus  l'infini           " + Double.POSITIVE_INFINITY);

  }
}

Exemple %%CHAPITRE%%.%%Exemple%% Démonstration des wrappers

Cet exemple peut paraître bien compliqué. Nous verrons prochainement en détail toutes les subtilités qu'il peut contenir. Pour l'instant, il vous suffit de connaître les points suivants :

Le type booléen

Le langage JAVA propose en standard un type booléen nommé boolean. Deux constantes respectivement nommées true et false sont respectivement associées aux valeurs vrai et faux.

Contrairement au C, il n'est pas possible d'utiliser une expression entière là où l'on devrait typiquement trouver une expression booléenne. Par exemple, le code suivant est illégal :

int i;

while (i)
{
  // Gros calcul
}

Il faut utiliser :

int i;

while (i != 0)
{
  // Gros calcul
}

Ceci peut paraître très contraignant au programmeur C ou C++ mais l'on s'y habitue très vite et cela présente l'avantage d'exercer une séparation forte entre le type booléen et les entiers, ce qui est totalement justifié. En outre cela permet d'éviter une erreur typique du débutant en C. En effet, en Java, l'instruction suivante (où le programmeur a très probablement confondu l'opérateur d'affectation = avec celui de comparaison ==) est illicite car le test doit impérativement être booléen :

int i;

if (i = 5)  // Erreur probable, confusion avec i==5
  instruction;

Le langage JAVA propose la panoplie habituelle des opérateurs booléens auxquelles s'ajoutent les opérateurs binaires du C :

Opérateur Fonction Domaine d'application Remarque
&& ET Logique Booléens exclusivement Evaluation en court circuit
|| OU Logique
! NON Logique

/

& ET Binaire Booléens et entiers Evaluation totale
| OU Binaire
^ OU Exclusif Binaire
~ NON Binaire

 /

Quelle différence y'a t'il entre l'application d'un opérateur logique booléen ou logique sur des variables booléennes ? Tout simplement le mode d'évaluation. En effet, considérez l'expression booléenne a && b. L'évaluation a lieu de gauche à droite. Si a est fausse, alors b ne sera pas évaluée car la valeur finale de l'expression est déja connue (respectivement a || b si a est vraie). Cela permet d'écrire sans danger une expression telle que :

if ((a != null) && (a.methode() .... )

Où l'appel a.methode() déclencherait à coup sur une exception si a était nul.

En revanche, il y a des cas où l'on souhaite que tous les membres d'une expression soient impérativement évalués. Ceci est garanti par l'utilisation des opérateurs booléens binaires qui n'utilisent pas de court circuit.

Le type boolean est lui aussi associé à une classe wrapper : Boolean.

Le type char

Décidément, le JAVA élimine toutes les utilisations parallèles que le C pouvait faire des entiers. En effet, le type char n'est pas similaire à un petit entier mais possède ses règles de fonctionnement propre. Notons immédiatement que pour des raisons évidentes de portabilité, un caractère JAVA est toujours codé en UNICODE et non pas comme bien souvent en C à un petit entier sur 8 bits.

Il existe 3 moyens de spécifier un caractère JAVA :

  1. Taper en dur un caractère entre apostrophes
  2. Utiliser une séquence d'échappement du C, telle que '\t' pour la tabulation ou un code ASCII inférieur à 127 (la plage commune de l'UNICODE et du code ASCII) sous la forme '\ooo'o est un chiffre octal. La présence d'exactement 3 chiffres est requise.
  3. Utiliser le code UNICODE sous la forme '\uxxxx'x est un chiffre hexadécimal. Ici encore, il est impératif de fournir les 4 chiffres.

Le programme suivant montre comment spécifier de 3 manières différentes le caractère correspondant au chiffre 0.

public class EssaiChars
{
  static public void main(String args[])
  {
    char ch1='0';
    char ch2='\060';   // Code ASCII 48 en octal
    char ch3='\u0030'; // Ouf ! les 127 premieres valeurs
                       // correspondent au code ASCII

    System.out.println(ch1);
    System.out.println(ch2);
    System.out.println(ch3);
  }
}

Exemple %%CHAPITRE%%.%%Exemple%% Présentation du type char

La classe wrapper associée au type char est nommée Character. Elle contient des méthodes (statiques) de test importantes qui permettent, en particulier, de savoir à quel « type » appartient un caractère. Elle propose également des méthodes permettant de passer de la forme textuelle du caractère à son code UNICODE et respectivement.

Méthode Rôle
static boolean isDigit(char) renvoie true si le caractère fourni en paramètre est un chiffre
static boolean isLetter(char) renvoie true si le caractère fourni en paramètre est une lettre
static boolean isLetterOrDigit(char) renvoie true si le caractère fourni en paramètre est une lettre ou un chiffre
static boolean isWhitespace(char) renvoie true si le caractère fourni en paramètre est un «  blanc » au sens du Java ce qui inclue le caractère espace et la tabulation.
static boolean isLowerCase(char) renvoie true si le caractère fourni en paramètre est une lettre minuscule
static boolean isUpperCase(char) renvoie true si le caractère fourni en paramètre est une lettre majuscule
static int getType(char ch) renvoie le type du caractère conformément à une batterie de constantes définies dans la classe Character
static int getNumericValue(char ch) renvoie le code UNICODE du caractère considéré

Table %%CHAPITRE%%.%%Table%% Quelques méthodes de la classe Character

Le type String

Le langage Java propose une classe String qui permet de manipuler très facilement des chaînes de caractère sans avoir à utiliser le formalisme des tableaux de caractères du C.

Les chaînes étant des objets il sera possible d'invoquer des méthodes sur les chaînes de caractères. Toutefois vu leur spécificité, il est également possible de les utiliser comme un type de base. Ainsi, il n'est pas nécessaire d'utiliser le mot clef new de création d'objet pour initialiser une chaîne de caractère à partir d'une constante.

De même, l'opérateur + est surchargé de manière à réaliser instantanément des concaténations de chaînes de caractères. En outre, et comme le montre l'exemple suivant, si vous disposez d'une chaîne de caractères s et d'un objet x, l'expression s + x est équivalent à s+x.toString(). Pour résumer, lorsque vous utilisez l'opérateur + dans une expression arithmétique dont le membre de gauche est une chaîne de caractères, tous les membres sont convertis implicitement en chaînes de caractères par appel implicite de la méthode toString .

public class EssaiString
{
  static public void main(String args[])
  {
    String chaine1="Une chaine sans accent";
    String chaine2="Une chaîne avec accents ? ";
    String chaine3="Une cha\u00E2ne avec accents !";

    String chaine4="toto";
    String chaine5="toto";

    int    heure=13;

    System.out.println(chaine1);
    System.out.println(chaine2);
    System.out.println(chaine2 + " " + chaine3);
    System.out.println("Il est " + Integer.toString(heure) + " heures");
    System.out.println("Il est " + new Integer(heure).toString() + " heures");
    System.out.println("Il est " + heure + " heures");

    System.out.println(chaine4);
    System.out.println(chaine5);

    System.out.println(chaine4==chaine5);
    System.out.println(chaine4.compareTo(chaine5));

  }
}

Exemple %%CHAPITRE%%.%%Exemple%% Présentation du type String

La plupart des classes de l'API disposent d'une méthode toString() qui leur donne une représentation visuelle convenable. Si vous souhaitez pouvoir afficher vos objets (le plus souvent à des fins de débogage), plutôt que de créer une méthode afficher() les imprimant à l'écran, il est de bon aloi de redéfinir la méthode toString() qui renvoie une chaîne de représentation de l'objet. Cette dernière méthode étant alors utilisée automatiquement dans une expression telle que System.out.println(x). Nous aurons l'occasion de fournir des exemples de cette technique.

Une petite astuce surprenante lors de la première rencontre : les programmeurs JAVA étant de grands fainéants devant l'éternel, ils préfereront souvent écrire ""+x que x.toString() pour convertir un objet x en chaîne de caractère.

Seule ombre au tableau, l'opérateur == n'a pas été surchargé de manière à fonctionner sur les chaînes de caractères. L'expression a == ba et b sont des String ne renverra true que si les deux variables sont des références sur le même objet. Si vous souhaitez tester l'égalité lexicographique entre 2 chaînes, il vous faudra utiliser a.equals(b). La méthode compareTo() fournit un résultat numérique comparable au retour de la fonction c stcmp.

Toutes les fonctions de manipulation de chaînes de caractères sont ici des méthodes de la classe String. Nous donnons ici une liste très partielle des fonctionnalités proposées :

Méthode But Equivalent C
length() Renvoie la longueur de la chaîne strlen
Opérateur + Concaténation de deux chaînes strcat
toLowerCase() Mise en minuscules tolower
toUpperCase() Mise en majuscules toupper
indexOf() Renvoie la position d'un caractère ou d'une sous chaine index, strchr
substring() Renvoie des souschaines  

Table %%CHAPITRE%%.%%Table%% Quelques opérations sur les chaînes

Afin d'être totalement complet, notons que la concaténation de deux chaînes de caractère est en fait réalisée au travers de la méthode append de la classe StringBuffer. En effet, par nature, une String est immuable. Si vous voulez la modifier, vous devez créer un nouvel objet qui contiendra la copie modifiée de votre chaîne initiale.

Tout au contraire, la classe StringBuffer est une suite de caractères destinée à être modifiée par ajout ou suppression de certains de ces éléments. Dès lors, si vous avez besoin d'une chaîne de caractères qui va être modifiée au cours de son existence, pensez à StringBuffer.

Les instructions

Le jeu d'instructions du langage JAVA est fortement inspiré de celui de son ancètre le C à une exception près : le fameux goto a disparu. Notons cependant que si l'instruction a disparu, goto est néanmoins un mot réservé du langage.

Les instructions se découpent en 3 grandes catégories :

  1. Les instructions simples qui comprennent les affectations et les appels de méthodes.
  2. Les structures de programmation qui elles mêmes se subdivisent en :
    1. Les choix (simple ou multiple)
    2. Les répétitions
  3. Les blocs d'instructions

Les instructions simples

Les instructions simples sont celles qui constituent d'elles mêmes une simple ligne de programmation. Elles comportent les affectations et les appels de méthodes.

L'affectation

C'est assurément la plus simple des instructions. Son fonctionnement diffère selon que l'on affecte un type élémentaire ou un objet. En effet, lorsque vous affectez un type simple, la valeur de droite est intégralement recopiée dans la variable à gauche du signe égal. Ensuite, les deux variables vivent séparément.

Il en est tout autre avec des objets. En JAVA, les objets sont toujours manipulés via des références. Vous pouvez voir une référence comme une poignée sur un objet. Lorsque vous affectez une variable à une autre, cela revient à rajouter une poignée sur l'objet situé à droite du signe égal. Cela vous confère donc 2 moyens différents à un même objet. Ainsi, toute modification sur l'une des 2 variables est instantanément répercutée sur l'autre.

De fait, la comparaison entre objets s'effectue avec la méthode equals. En effet, comparer deux handle revient uniquement à vérifier qu'ils font référence au même objet.

Les appels de méthode

Appeler une méthode constitue une instruction simple. Nous y reviendrons en détail dans les chapitres concernant la programmation orientée objet. Pour l'instant, il vous suffit de considérer que cette opération est équivalent à l'appel d'un sous programme en programmation classique.

Les structures de programmation

Les structures de choix

A l'instar du C ou du C++, JAVA propose 2 mécanismes de choix :

Les choix simples

Il s'agit ici de différencier le fonctionnement du programme en fonction de la valeur d'une expression booléenne, par exemple : un test.

Le schéma général est le suivant : (les parties en italique sont facultatives)

if (expression)
  instruction1;
else
  instruction2;
instruction3;

Si expression est évaluée à true, alors instruction1 sera exécutée, sinon ce sera instruction2. Ensuite, le contrôle reviendra à instruction3. Si vous devez exécuter plus d'une seule instruction, vous avez la possibilité d'utiliser un bloc d'instructions c'est à dire un ensemble d'instructions entourées par des accolades.

Par exemple, supposons que nous programmions la simulation de croissance d'une plante. Le taux d'humidité est assurément une variable à tenir en compte. Le test suivant modifie les valeurs du taux de croissance de la tige et des racines en fonction d'une valeur seuil :

final int SEUIL_HUMIDITE=10;
int   tauxCroissanceTige;
int   tauxCroissangeRacines;
int   tauxHumidite;

if (tauxHumidite < SEUIL_HUMIDITE)
{
  tauxCroissanceTige=5;
  tauxCroissanceRacines=3;
}
else
{
  tauxCroissanceTige=9;
  tauxCroissanceRacines=8;
}
// Reste du programme

Autre point intéressant de ce programme : l'utilisation du mot clef final qui permet de définir une constante, ici nommée SEUIL_HUMIDITE. Il est également possible d'enchainer les tests en combinant la partie else d'un premier if avec un second test comme dans l'exemple suivant où l'on a plusieurs valeurs de seuil qui sont successivement testées :

final int SEUIL_DESERT=1;
final int SEUIL_SECHERESSE=5;
final int SEUIL_STANDARD=10;
final int SEUIL_HUMIDE=15;
int   tauxCroissanceTige;
int   tauxCroissangeRacines;
int   tauxHumidite;

if (tauxHumidite < SEUIL_DESERT)
{
  tauxCroissanceTige=0;
  tauxCroissanceRacines=0;
}
else if (tauxHumidite < SEUIL_SECHERESSE)
{
  tauxCroissanceTige=2;
  tauxCroissanceRacines=1;
}
else if (tauxHumidite < SEUIL_STANDARD)
{
  tauxCroissanceTige=4;
  tauxCroissanceRacines=2;
}
else if (tauxHumidite < SEUIL_HUMIDE)
{
  tauxCroissanceTige=5;
  tauxCroissanceRacines=3;
}
else
{
  tauxCroissanceTige=9;
  tauxCroissanceRacines=8;
}
// Reste du programme

Comme dans la plupart des langages de programmation modernes, une instruction else est toujours raccordée au dernier if rencontré, sauf si le code est structuré en blocs impliquant une structure différente.

Les choix multiples

L'instruction de choix multiple est liée à la notion de discriminant. C'est à dire une variable atomique discrète par rapport aux valeurs de laquelle on va modifier le flot d'exécution du programme. L'idée est de créer un cas d'utilisation pour chaque valeur particulière du discriminant.

La syntaxe étant pour le moins tordue, nous commencerons par un exemple simple. Le discriminant est ici une variable entière nommée choix.

int choix;

// Code modifiant la valeur de choix

switch (choix)
{
  case 1:
    i=3;
    j*=2;
    break;
  case 2:
  case 3:
    i=6;
    j=3*i;
    break;
  case 4:
    i-=j;
    j=2;
    break;
  default:
    System.out.println("La valeur " + choix + " est invalide");
    break; 
}

Les points suivants sont intéressants à retenir :

Les boucles

Il y a trois structures de boucles en JAVA associées respectivement aux mots clefs while, do/while, et for ainsi que 2 mots clefs de contrôle des boucles continue et break.

La boucle while

C'est l'instruction de boucle de base. Elle signifie littéralement :

Tant qu'une condition est vérifiée, effectuez les instructions suivantes

Comme vous le voyez, l'essence même de la bouche while repose sur la nature de la condition de boucle. Celle-ci doit bien entendu être une expression booléenne. On appelle habituellement l'ensemble d'instructions à exécuter le corps de la boucle.

L'exemple suivant effectue un calcul arithmétique jusqu'à convergence :

while ((x-y)/x < epsilon)
{
  x = ... // resultat d'un gros calcul
  y=x;
}

A l'instar des instructions de test, il vous est possible de faire porter la boucle while sur une instruction simple ou sur un bloc d'instructions. Une erreur classique consiste à mettre un point-virgule à la fin de la ligne contenant while. Ce qui signifie que vous allez répéter une instruction vide qui ne se finira sans doute jamais.

La boucle while est la seule dont vous avez réellement besoin, les boucles do/while et for ne sont là que pour le confort du programmeur.

La boucle do/while

A l'instar de la boucle while, la bouche do/while exécute une série d'instructions tant qu'une expression de contrôle est vraie. La grande différence tient au fait que la boucle while effectue la vérification avant le corps alors que dans une boucle do/while, la vérification s'effectue en fin de boucle.

La conséquence est simple : avec une boucle while, le corps ne sera jamais exécuté si la condition est fausse au début de la première itération alors qu'avec une boucle do/while vous êtes assuré que le corps sera exécuté au moins une première fois.

La boucle do/while est donc une structure très bien adaptée aux cas où il est nécessaire de faire au moins une fois l'action prévue dans le corps de la boucle. Dans l'exemple suivant, on lit une variable au clavier tant que la valeur de celle-ci n'est pas comprise dans un intervalle prédéterminé. Notez au passage le nombre impressionnant d'instructions nécessaires pour la lecture d'une valeur entière au clavier en JAVA.

import java.io.*;

public class TestIntervalle
{
  public static void main (String args[])
  {
    // On commence par déclarer un objet lecteur sur le clavier
    // Le clavier est associé à la variable System.in
    // Un InputStreamReader (lecteur sur un flux) est créé dessus
    // Puis on ajoute une couche BufferedReader nécessaire pour lire des
    // informations par ligne
    BufferedReader lecteurClavier=new BufferedReader(new InputStreamReader(System.in));

    int            valeur;
    String         chaineLue;

    final int VALEUR_INFERIEURE=5;
    final int VALEUR_SUPERIEURE=10;

    try
    {
      do
      {
        // Lecture d'une ligne au clavier
        chaineLue=lecteurClavier.readLine();
        // Conversion de la chaine en entier
        valeur=Integer.parseInt(chaineLue);
      } while ((valeur < VALEUR_INFERIEURE) || (valeur > VALEUR_SUPERIEURE));
    }
    catch (Exception e)
    {
      System.out.println("Erreur d'E/S " + e.getMessage());
    }
  }
};

Les instructions try et catch doivent vous paraîtres un peu exotiques. Elles seront analysées en détails dans le chapitre concernant les exceptions. Pour l'instant, sachez qu'il s'agit d'un mécanisme très évolué de gestion des erreurs d'exécution basé sur les éléments suivants :

La boucle for

Le JAVA a également hérité de la bouche for du C. Contrairement à d'autres langages où elle est spécialisée dans l'exécution répétitive d'un bloc d'instructions respectivement à la valeur d'un compteur entier, la boucle for est très versatile en C et dérivés. En fait, il s'agit simplement d'une réécriture de la boucle while.

Elle se présente de la manière suivante :

for ( instructions_initialisation ;
      instruction_test ;
      instructions_mise_a_jour )
  corps_de_boucle;

où :

En fait, vous l'aurez compris, on peut réécrire la boucle for de la manière suivante :

instructions_initialisation;
while (instruction_test)
{
  corps_de_boucle;
  instructions_mise_a_jour;
}

Elle est toutefois très appréciée des programmeurs de par son extrème concision qui amène des aberrations où toute la boucle est logée dans les trois sous expressions.

L'exemple suivant fait la somme des éléments d'un tableau d'entiers

import java.io.*;

public class TestFor
{
  final static int TAILLE_TABLEAU=10;

  public static void main (String args[])
  {
    int tableau[]=new int [TAILLE_TABLEAU];
    int somme;

    // code de remplissage du tableau omis

    somme=0;
    for (int i=0;
         i<TAILLE_TABLEAU;
         i++)
      somme+=tableau[i];

    System.out.println("La somme vaut " + somme);
  }
};

L'un des grands intérêts de la boucle for est de permettre la déclaration des variables de boucle dans la première sous expression et manière à ce qu'elles ne débordent pas de la boucle. Dans l'exemple précédent, la variable i n'existe plus à la sortie de la boucle. Si vous vous sentez encore peu familier avec cette structure, voici comment l'on pourrait l'écrire à l'aide d'une boucle while :

    somme=0;
    i=0;
    while (i<TAILLE_TABLEAU)
    {
      somme+=tableau[i];
      i++;
    }
    System.out.println("La somme vaut " + somme);

On peut également programmer comme un cochon avec une boucle for, par exemple :

    somme=0;
    for (int i=0;
         i<TAILLE_TABLEAU;
         somme+=tableau[i++]);

Ici, l'on a aggloméré une instruction faisant typiquement partie du corps de la boucle - qui, du coup, se retrouve réduit à une instruction vide - dans la troisième sous expression. Ce genre de code est certes légal mais ne facilite pas du tout la compréhension du programme et doit donc être éliminé.

Il est également recommandé de ne jamais modifier la valeur du compteur de boucle dans le corps de celle-ci.

Les instructions de rupture break et continue

Généralités

Le but des instructions break et continue est de pouvoir remonter d'un niveau de programmation hiérarchique, que ce soit une boucle ou une structure conditionnelle.

Leur fonctionnement est toutefois assez différent :

Afin d'illustrer le comportement de break, reprenons l'exemple sur la boucle do while qui saisissait des nombres jusqu'à satisfaction d'une certaine condition. Nous pouvons l'écrire :

import java.io.*;

public class TestBreak
{
  public static void main (String args[])
  {
    // On commence par déclarer un objet lecteur sur le clavier
    // Le clavier est associé à la variable System.in
    // Un InputStreamReader (lecteur sur un flux) est créé dessus
    // Puis on ajoute une couche BufferedReader nécessaire pour lire des
    // informations par ligne
    BufferedReader lecteurClavier=new BufferedReader(new InputStreamReader(System.in));

    int            valeur;
    String         chaineLue;

    final int VALEUR_INFERIEURE=5;
    final int VALEUR_SUPERIEURE=10;

    try
    {
      System.out.println("Saisissez une valeur comprise entre " + VALEUR_INFERIEURE + " et " + VALEUR_SUPERIEURE);
      while (true)
      {
        // Lecture d'une ligne au clavier
        chaineLue=lecteurClavier.readLine();
        // Conversion de la chaine en entier
        valeur=Integer.parseInt(chaineLue);

       	if ((valeur >=  VALEUR_INFERIEURE) && (valeur < VALEUR_SUPERIEURE))
       	  break;
       	else
       	  System.out.println("Vous n'avez pas saisi une valeur dans le bon intervalle, recommencez");
      }
    }
    catch (Exception e)
    {
      System.out.println("Erreur d'E/S " + e.getMessage());
    }
  }
}

Le résultat est alors :

Saisissez une valeur comprise entre 5 et 10
3
Vous n'avez pas saisi une valeur dans le bon intervalle, recommencez
11
Vous n'avez pas saisi une valeur dans le bon intervalle, recommencez
6

Vous noterez le while(true) qui créée une boucle infinie dont on ne peut sortir que par l'intermédiaire du break. C'est un cas typique d'utilisation de break : la sortie en milieu de boucle. Bien sur, on peut atteindre le même résultat avec une boucle while, mais il faudra plus réfléchir.

L'instruction continue quand à elle place le pointeur d'exécution directement en fin de boucle. C'est à dire juste avant le test dans le cas d'une boucle while ou do/while et avant l'instruction de mise à jour dans le cadre d'une boucle for. Considérons par exemple l'exemple suivant :

for (int i=0;i<TAILLE_TABLEAU;i++)
{
  Premier traitement;
  if (tableau[i] < eps)
    continue;
  Second traitement;
}

Au cas où la condition tableau[i]<eps est remplie, la partie intitulée Second traitement est passée sous silence, et l'on exécute immédiatement i++ avant d'effectuer le test. Cela permet de court-circuiter une partie du corps de la boucle. Bien entendu, on pouvait obtenir le même résultat en changeant le sens de la condition.

Plus encore que cette forme simple, c'est la forme étiquettée des instructions break et continue qui est intéressante. En effet, dans certains cas, l'on a envie de sortir de plusieurs boucles à la fois. Dans un langage comme C ou C++, vous pouvez réaliser cette opération simplement à l'aide d'une instruction goto. Sans vouloir entrer dans la polémique qui entoure cette décision, les concepteurs de Java n'ont pas jugé souhaitable d'inclure une instruction goto dans le langage (notons toutefois que le mot est réservé). Toutefois, ils ont offert la possibilité d'étiquetter les boucles pour effectuer des continue ou des break sur étiquette.

Le fonctionnement est simple :

Pour placer une étiquette sur une boucle, il suffit de mettre une instruction label: avant le code de la boucle.

L'exemple suivant devrait permettre d'éclaircir la situation :

BoucleExterne:
while (condition1)
{
  ...
  BoucleInterne:
  for (exp1 ; exp2 ; exp3)
  {
    for (... ; ... ; ...)
    {
      ...
      if (condition2)
        continue BoucleInterne;
      ...
    }
    while (...)
    {
      ...
      if (condition3)
        break BoucleExterne;
      ...
    }

  }
  ...
}
CodeFinal
  1. Lorsque la condition condition2 est satisfaite, le pointeur d'exécution est placé directement sur exp3, partie mise à jour de la boucle étiquetée BoucleInterne:.
  2. Lorsque la condition condition3 est satisfaite, le pointeur d'exécution est placé directement sur code final, c'est à dire juste après la boucle étiquetée BoucleExterne.