La conditionnelle


La programmation n'aurait que très peu d'intéret si elle ne permettait pas aux ordinateurs de réagir différemment selon différentes conditions.

Une machine programmable doit pouvoir effectuer des traitements différents selon les données qu'elle doit traiter.

Cela est possible grâce à l'existence des structures de controles dans les langages de programmation : la conditionnelle et les boucles.

L'objet de cette partie du cours est d'expliquer le fonctionnement d'une conditionnelle.

I - Le if

La conditionnelle if est une structure de contrôle permettant d'exécuter ou de ne pas exécuter certaines instructions en fonction de certaines conditions.

En francais, on peut la traduire par un si. Si telle condition est vérifiée alors exécuter telle action. C'est le If simple.

On peut également préciser ce qu'il faut faire lorsque la condition n'est pas réalisée: si telle condition est vérifiée alors exécuter telles instructions sinon exécuter telles instructions. C'est la structure Si ... Alors ... Sinon qui se traduit en Java par un If suivi d'une partie Else.

I-1 - Le if simple

Voici une manière d'écrire un if simple en Java:

if ( condition )
{
    Instructions à exécuter
    si la condition est vraie
}

La condition n'est rien d'autre qu'une expression logique.

Les instructions entre accolades ne seront exécutées que si la condition est vraie.

S'il n'y a qu'une seule instruction à exécuter, on peut omettre les accolades, mais cette instruction devra être suivie d'un point virgule :

if ( condition ) Instruction à exécuter ;

Prenons un exemple :

if ( n > 0 ) n-- ; 

Si par exemple n vaut 5 juste avant l'exécution du if, la condition n > 0 est vérifiée. L'ordinateur va donc exécuter l'instruction de décrémentation n-- . n vaudra donc 4 à la sortie de la conditionnelle.

Si n vaut -1 par contre, la condition n > 0 n'est pas vérifiée. L'affectation ne sera pas exécutée et n vaudra toujours -1 à la sortie de la conditionnelle.

I-2 - Le if avec partie else

Le if avec partie else permet d'exécuter certaines instructions si une condition est vraie et d'autres, si elle est fausse.

Voici une manière de l'écrire:

if ( condition )
{
    Instructions à exécuter
    si la condition est vraie
}
else
{
   Instructions à exécuter 
   si la condition est fausse 
}

De même, les accolades ne sont pas nécessaires s'il n'y a qu'une seule instruction.

Prenons un exemple :

if ( n > 0 )
   n--;
else
   n++;

Que se passe-t-il lors de l'exécution de cette conditionnelle ?

Supposons par exemple que n vale 5 juste avant l'exécution du if. La condition n > 0 est vérifiée. L'ordinateur va donc exécuter la décrémentation n--. Par contre, l'incrémentation n++ ne sera pas exécutée. n vaudra donc 4 à la sortie de la conditionnelle.

Si n vaut -1 par contre, la condition n > 0 n'est pas vérifiée. L'affectation n-- n'est donc pas exécutée. Mais comme la condition est fausse, l'affectation n++ sera exécutée. n vaudra donc 0 à la sortie de la conditionnelle.

I-3 - ifs imbriqués

Les instructions à l'intérieur d'un if ne sont pas forcément des affectations comme dans les exemples précédents.

On peut également y mettre d'autres structures de controle et en particulier d'autres ifs. En fait, pour le compilateur une structure de contrôle est une instruction. Gardez ceci à l'esprit. Cela vous permettra de comprendre les inévitables erreurs de compilation auxquelles vous serez confrontées.

Pour éclaircir les choses, prenons quelques exemples de ifs imbriqués (les exemples présentés ici se trouvent dans le projet If-Imbrique).

Exemple 1
if ( n > 0 )
    n--;
else
    if ( n >= -3 )
         n++;
    else
         n = 2 * n;

Ce code doit être interprété de la manière suivante: si n est plus grand que zéro alors décrémenter n. Sinon, si n est supérieur ou égal à -3 incrémenter n, sinon multiplier n par 2.

Pour n=5 l'instruction exécutée est n--. n vaudra donc 4 à la sortie de la conditionnelle.

Pour n=-1 l'instruction exécutée est n++. n vaudra donc 0 à la sortie de la conditionnelle.

Pour n=-4 l'instruction exécutée est n = 2 * n. n vaudra donc -8 à la sortie de la conditionnelle.

De manière générale:

Exemple 2

Sans aucune règle ou convention, il est impossible de savoir si dans le code suivant:

if ( n > 0 )
    if ( n > 3 )
         n++;
    else
         n--;

le else se rapporte à la première condition (n > 0 ) ou à la deuxième (n > 3).

Dans le premier cas n-- serait exécutée pour tous les nombres inférieurs ou égaux à zéro, alors que dans le deuxième cas, cette instruction ne serait exécutée que pour les nombres 1, 2 et 3.

La règle utilisée par le compilateur java est la suivante:

Par défaut un else se rapporte au dernier if rencontré.

Dans le code précédent le else se rapporte donc à la condition (n > 3):

Exemple 3

Comment faire alors si l'interprétation du compilateur ne correspond pas à ce que l'on souhaite faire ?

La solution se trouve dans l'utilisation des accolades, car elles permettent de modifier l'interprétation du code par le compilateur.

Dans l'exemple précédent, si l'on souhaite que le else se rapporte à la première condition (n > 0), il suffit d'écrire les choses de cette manière:

if ( n > 0 )
    { if ( n > 3 ) n++; }
else
    n--;

En exécutant ce code, vous constaterez qu'il ne donne pas les mêmes résultats que celui de l'exemple 2:

II - Switch

La conditionnelle switch est utilisée lorsque l'on veut effectuer différents traitements selon la valeur d'une expression. La même chose peut être réalisée avec des ifs, mais en utilisant switch on obtient en général un code plus court.

Voilà la syntaxe d'un switch:

switch ( Expression ) 
{
       case V1 :
         Instructions à exécuter lorsque 
         l'expression vaut V1 ; break;
       case V2 :
         Instructions à exécuter lorsque 
         l'expression vaut V2 ; break;
       .
       .
       case VN :
         Instructions à exécuter lorsque 
         l'expression vaut VN ; break;
       default :
      Instructions à exécuter dans les autres cas ;  
}

L'expression entre parenthèses doit être de type byte, short, char ou int. Pour nous ce sera obligatoirement une expression de type int car les types byte, short et char ne seront pas vus dans ce cours.

V1 , V2 , ...., VN sont des constantes qui doivent être de même type que l'expression. Dans notre cas, ce seront donc des nombres entiers.

La partie default est optionnelle.

break est une instruction qui permet de sortir de certaines structures de contrôle. En particulier, dans une structure de controle switch, son exécution provoque le saut systématique (on parle de branchement inconditionnel) à la première instruction qui suit la fin du switch.

Dans une structure de contrôle switch, l'instruction break est indispensable.

Voici, par exemple comment déterminer le nom d'un mois en fonction de son numéro avec une structure de contrôle switch:

 
switch ( NumeroMois  )
{
     case 1 : NomMois = "Janvier"; break;
     case 2 : NomMois = "Fevrier"; break;
     case 3 : NomMois = "Mars"; break;
     case 4 : NomMois = "Avril"; break; 
     case 5 : NomMois = "Mai"; break;  
     .
     .
     case 12 : NomMois = "Decembre"; break;     
     default :
       es.AfficherMessage ("Numero de mois inexistant!");
}

Supposons par exemple que l'on ommette l'instruction break dans le troisième case:

 
switch ( NumeroMois  )
{
     case 1 : NomMois = "Janvier"; break;
     case 2 : NomMois = "Fevrier"; break;
     case 3 : NomMois = "Mars"; 
     case 4 : NomMois = "Avril"; break; 
     case 5 : NomMois = "Mai"; break;  
     .
     .
     case 12 : NomMois = "Decembre"; break;     
     default :
       es.AfficherMessage ("Numero de mois inexistant!");
}

que se passera t-il ?

Tout ira bien, sauf lorsque le numéro du mois sera égal à 3. Dans ce cas l'ordinateur exécutera l'instruction NomMois = "Mars"; , mais comme elle n'est pas suivie d'un break, il continuera à exécuter le cas suivant, c'est à dire l'instruction NomMois = "Avril";. Au final, le résultat sera "Avril", lorsque le numéro du mois vaut 3.

III - Protection contre les erreurs

Une application importante des conditionnelles est la protection d'un programme contre les erreurs dues à l'utilisateur. Nous distinguerons deux types d'erreurs:

  1. les données sont erronées: typiquement un champ de texte est mal rempli ou pas rempli.
  2. l'ordre des actions de l'utilisateur sur l'interface graphique n'a pas de sens. Dans notre cas, les actions sont limitées (pour l'instant du moins) à des clics sur des boutons. Dans certains cas, il faut pouvoir obliger l'utilisateur à cliquer sur les boutons dans un certain ordre. Nous verrons cela sur un exemple.
III-1 - Erreur dans les données

La protection d'un programme contre les données erronées peut se faire avec une imbrication de ifs. Mais cela peut devenir très lourd lorsqu'il y a beaucoup de données à tester. Nous allons voir ici un moyen d'alléger cette écriture, en utilisant l'instruction return.

Pour illustrer ceci, prenons le projet DateValide que vous trouverez dans les exemples de projets de ce cours. Voici son interface graphique:

DateValide.jpg, 20kB

Ce programme teste la validité d'une date définie par un jour (nombre entier entre 1 et 31), un mois (nombre entier entre 1 et 12) et une année de ce siècle (nombre entier entre 2000 et 2099). Ces trois données sont lues depuis trois champs de texte (respectivement CT_Jour, CT_Mois et CT_Annee) dans des variables entières, respectivement Jour, Mois, Annee.

Mais avant de lire les données, nous testons qu'elles ont bien été saisies. Pour cela nous utilisons la fonction es.ChampDeTexteVide de la librairie JavaETBib. Cette fonction booléenne (une fonction qui retourne un résultat de type booléen) que vous serez amené à utiliser dans les exercices, permet de tester si un champ de texte donné est vide (dans ce cas le résultat retourné est true).

Dans la procédure évènementielle du premier bouton, le test se fait avec des ifs imbriqués sans utiliser l'instruction return. Voilà le résultat:

private void BT_Test_Sans_ReturnActionPerformed(...) 
{int Jour, Mois, Annee;
    
if (es.ChampDeTexteVide(CT_Jour))
es.AfficherMessage("Veuillez saisir le jour!");
else
  {Jour = es.LireEntier(CT_Jour);
    if (es.ChampDeTexteVide(CT_Mois))
    es.AfficherMessage("Veuillez saisir le mois!");
    else
       {Mois = es.LireEntier(CT_Mois);
        if (es.ChampDeTexteVide(CT_Annee))
        es.AfficherMessage("Veuillez saisir l'année!");
        else
        {Annee = es.LireEntier(CT_Annee);
        if ((Jour < 1) || (Jour > 31))
        es.AfficherMessage("Numéro de jour non valide");
        else
            if ((Mois < 1) || (Mois > 12))
            es.AfficherMessage("Numéro de mois non valide");
            else
              if ((Annee < 2000) || (Annee > 2099))
              es.AfficherMessage("Année non valide"); 
              else es.AfficherMessage("Date valide"); 
         }
       }
  }
}                                                   
   

Vous conviendrez que ce code est particulièrement peu lisible. Pour raccourcir ce genre d'écriture, on peut utiliser l'instruction return, qui permet de forcer la fin de l'exécution du sous-programme qui la contient. Avec cette instruction, l'exemple précédent peut être écrit sans aucune imbrication de ifs de la manière suivante:

private void BT_Test_Avec_ReturnActionPerformed(...) 
{int Jour, Mois, Annee;

if (es.ChampDeTexteVide(CT_Jour))
{es.AfficherMessage("Veuillez saisir le jour!"); return;}
Jour = es.LireEntier(CT_Jour);
    
if (es.ChampDeTexteVide(CT_Mois))
{es.AfficherMessage("Veuillez saisir le mois!"); return;}
Mois = es.LireEntier(CT_Mois);
    
if (es.ChampDeTexteVide(CT_Annee))
{es.AfficherMessage("Veuillez saisir l'année!"); return;}
Annee = es.LireEntier(CT_Annee); 
    
if ((Jour < 1) || (Jour > 31))
{es.AfficherMessage("Numéro de jour non valide"); return;}
   
if ((Mois < 1) || (Mois > 12))
{es.AfficherMessage("Numéro de mois non valide"); return; }

if ((Annee < 2000) || (Annee > 2099)) 
{es.AfficherMessage("Année non valide"); return; }
    
es.AfficherMessage("Date valide");
}

Note importante: dans le cours sur les sous-programmes nous avons vu que l'instruction return doit être utilisée dans une fonction pour retourner le résultat (l'expression figurant à droite du return). Dans l'exemple précédent nous utilisons une variante de l'instruction return, qui n'est suivie d'aucune expression. Ici l'instruction return ne sert pas à retourner un résultat, mais simplement à forcer le retour du sous-programme dans lequel elle est contenue. Ce sous-programme doit donc forcément être une procédure (évènementielle ou non).

III-2 - Actions désordonnées de l'utilisateur

Pour empêcher des actions désordonnées de l'utilisateur, il y a principalement deux solutions:

  1. Désactiver les composants qui ne doivent pas être utilisés à un moment donné. Cette solution est utilisée dans de nombreux logiciels. On constate par exemple que certains éléments de la barre d'outils ou des menus deviennent inactifs. Cette solution a le désavantage de ne pas être explicative: on ne dit pas à l'utilisateur pourquoi il ne peut pas faire ceci ou cela. On l'empêche simplement de le faire.
  2. Laisser agir l'utilisateur, mais l'avertir par un message d'erreur que l'opération ne peut pas être effectuée en lui expliquant pourquoi.

Notez que la première solution annule carrément tout évènement associé au composant désactivé et par conséquent la procédure évènementielle associée ne sera jamais activée !

Mais pour l'instant, nous n'envisagerons que la deuxième solution. La désactivation des composants sera vue ultérieurement lorsque nous approfondirons la conception des interfaces graphiques.

Dans la deuxième solution, nous avons besoin de trouver un moyen de tracer les actions de l'utilisateur. Comment savoir sur quels boutons il a cliqué avant qu'il clique sur un bouton donné ? Une manière de résoudre ce problème est d'utiliser des booléens. Voyons ceci sur un exemple.

Nous allons reprendre le projet FootBall vu en exercice dans le premier cours (vous trouverez également ce projet dans les exemples associés à ce cours, ce qui vous permettra de l'exécuter). Voici son interface graphique:

Football.jpg, 35kB

Le diagramme suivant donne l'ordre dans lequel les opérations doivent être enchainées:

OrdreActionFootBall.jpg, 62kB

Nous voulons interdire toutes les flèches marquées d'une croix rouge et autoriser les autres.

Vous serez probablement surpris de savoir qu'il suffit d'un seul booléen pour règler ceci !

Déclarons un booléen MatchEnCours. Nous allons nous arranger pour que la valeur de ce booléen soit true si et seulement si, il y a effectivement un match en cours. Au démarrage du programme, aucun match n'a commencé. Il est donc logique d'initialiser ce booléen à false:

boolean MatchEnCours = false; 

Occupons nous à présent du bouton Nouveau Match. Les instructions associées ne doivent être exécutées que s'il n'y a pas de match en cours. Autrement dit, lorsque MatchEnCours vaut false. D'autre part, lorsque ces instructions auront été exécutées, nous aurons un nouveau match en cours. Il faudra donc affecter la valeur true à MatchEnCours. Voici le nouveau code de la procédure évènementielle associée à ce bouton (les instructions ajoutées sont en blanc):

private void BT_NouveauMatchActionPerformed(...) {                                                
if ( MatchEnCours)
{es.AfficherMessage("Il y a un match en cours!");return;}
        
// On remet les scores à zéro
  ScoreEquipe1 = 0; ScoreEquipe2 = 0;
        
// On efface les noms des équipes du match précédent
  es.Effacer (CT_Equipe1); es.Effacer (CT_Equipe2);
        
// Pour afficher le score nul de départ
  es.Afficher (ScoreEquipe1, CT_ScoreEquipe1);
  es.Afficher (ScoreEquipe2, CT_ScoreEquipe2);   
        
MatchEnCours = true;
}     

Le bouton But quand à lui, ne doit être cliqué que s'il y a un match en cours. S'il n'y a pas de match en cours, nous affichons un message d'erreur et nous n'exécutons pas les instructions suivantes. Voici le nouveau code associé au bouton But de l'équipe 1:

private void BT_ButEquipe1ActionPerformed(...) {                                              
if (! MatchEnCours)
{es.AfficherMessage("Il n'a as pas de match en cours!");
return;}
        
ScoreEquipe1++;
es.Afficher(ScoreEquipe1, CT_ScoreEquipe1);       
}  

De même, le bouton Fin du match ne doit être cliqué que s'il y a un match en cours. Si cela est effectivement le cas, les instructions sont effectivement exécutées, puis nous affectons la valeur false à MatchEnCours (nous avons également ajouter des instructions pour vérifier que l'utilisateur a bien saisi les noms des deux équipes):

private void BT_FinMatchActionPerformed(...) {                                            
if (! MatchEnCours)
{es.AfficherMessage("Il n'a as pas de match en cours!");
return;}
        
// On récupère le nom des équipes, 
// mais avant on vérifie qu'elles ont bien été saisies
        
if (es.ChampDeTexteVide(CT_Equipe1))
{es.AfficherMessage("Veuilez saisir le nom de l'équipe 1!");
return;}
Equipe1 = es.Lire (CT_Equipe1);
        
if (es.ChampDeTexteVide(CT_Equipe2))
{es.AfficherMessage("Veuilez saisir le nom de l'équipe 2!");
return;}       
Equipe2 = es.Lire (CT_Equipe2);

// On les affiche dans les zones de texte
es.Afficher (Equipe1, ZT_Equipe1);
es.Afficher (Equipe2, ZT_Equipe2);

// et on affiche les scores dans les zones de texte
es.Afficher (ScoreEquipe1, ZT_ScoreEquipe1);
es.Afficher (ScoreEquipe2, ZT_ScoreEquipe2); 
        
MatchEnCours = false;
} 

Remarquez que le booléen que nous avons introduit ne nous dit pas réellement quels boutons on été cliqués. Il nous indique plutot un état du programme (l'état match en cours, dans notre cas), qui contient toute l'information nécessaire pour contrôler les clics.