Accès rapide :
La vidéo
L'instruction if / else
Aspects syntaxiques
Utiliser les templates de génération de code proposés par Eclipse
Comparatif avec les langages C/C++
Travaux pratiques
L'instruction switch / case
Limitation d'utilisation de l'instruction switch / case
Aspects syntaxiques
Le switch/case, une instruction optimisée
Cas des chaînes de caractères
Travaux pratiques
L'expression conditionnelle
Travaux pratiques
Cette vidéo vous montre comment utiliser les instructions conditionnelles en Java (if
/else
et switch
/case
).
Une comparaison entre les deux mécanismes est proposée. L'opérateur conditionnel est aussi présenté (le seul opérateur ternaire du langage).
L'instruction if
est une instruction conditionnelle : elle exécute des portions de code en fonction en fonction qu'une condition
soit vraie ou fausse (la condition doit calculer une valeur booléenne). Dans beaucoup de langages de programmation (Visual Basic, Pascal, ...)
l'instruction if
est aussi associée au mot clé then
: ce n'est pas le cas en Java.
Effectivement, comme nous l'avons dit à maintes reprises, Java s'inspire de la syntaxe du langage C dont il en reprend
bon nombre d'aspects. C'est donc aussi le cas pour le if
et en C le mot clé then
n'existe pas !
La condition doit être globalement parenthésée, ce qui retire l'obligation d'avoir un autre mot clé pour débuter le bloc de code dans le cas où la
condition est vraie. En voici un exemple.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
public class Demo { public static void main( String [] args ) { // La condition est globalement parenthésée if ( args.length == 0 ) { System.out.println( "Usage: java Demo files..." ); return; } // Suite du code } } |
Si vous oubliez les parenthèses autour de la condition, une erreur de compilation sera produite. Voici un exemple de code produisant cette erreur.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
public class Demo { public static void main( String [] args ) { // Attention, cela ne compile pas. Voir le message d'erreur ci-après if args.length == 0 { System.out.println( "Usage: java Demo files..." ); return; } // Suite du code } } |
Et voici le message d'erreur produit.
$> javac Demo.java Demo.java:6: error: '(' expected if args.length == 0 { ^ Demo.java:6: error: ')' expected if args.length == 0 { ^ 2 errors $>
Par contre, les accolades autour du code à exécuter, dans le cas où la condition est vraie, sont facultatives. Mais attention, dans ce cas,
seule une unique instruction pourra être exécutée. Regardez, dans l'exemple ci-dessous, les accolades ont étaient retirées et deux
affichages sont proposés. Il est vrai qu'un retrait (une indentation) vers la droite précède les deux affichages. Malgré cela,
le second affichage sera exécuté quelques soit le résultat calculé par la condition. En fait il suit la condition et aurait dû être placé au même niveau
que le if
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
public class Demo { public static void main( String [] args ) { if ( args.length == 0 ) System.out.println( "Usage: java Demo files..." ); System.out.println( "Autre affichage" ); /* Ce code aurait du être indenté ainsi, pour une meilleure compréhension if ( args.length == 0 ) System.out.println( "Usage: java Demo files..." ); System.out.println( "Autre affichage" ); */ // Suite du code } } |
Et voici les résultats produits suite à deux exécutions différentes de votre code.
$> javac Demo.java $> java Demo Usage: java Demo files... Autre affichage $> java Demo file1 file2 Autre affichage $>
Un autre mot clé est utile au if
: le mot clé else
. Il permet de lancer l'exécution d'un code dans le cas où la condition
renvoie l'état false
. De même que pour la partie de code à exécuter dans le cas où la condition calcule true
, les
accolades peuvent être mises ou non.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
public class Demo { public static void main( String [] args ) { if ( args.length == 0 ) System.out.println( "Usage: java Demo files..." ); return; } else { // Traitement des fichiers } } } |
if
/ else
).
Cela ne laisse aucune ambiguïté sur ce qui doit être exécuté ou non. De plus, si vous utilisez l'assistant Eclipse pour produire vos if
,
il générera systématiquement les accolages : c'est très bien. Pour rappel, tapez if
suivant le la séquence de touches CTRL + Espace, pour
activer l'assistant de génération de code.
Enfin, du point de vue de la syntaxe, notez qu'il n'existe pas de mot clé elif
ou elsif
en Java. Pour autant, il est possible
d'imbriquer plusieurs if
en cascade. L'exemple suivant transforme les valeurs numériques comprises entre 0 et 9 en leurs équivalents
textuels.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
public class Demo { public static void main( String [] args ) { int value = (int) (Math.random() * 10); if ( value == 0 ) { System.out.println( "Zéro" ); } else if ( value == 1 ) { System.out.println( "Un" ); } else if ( value == 2 ) { System.out.println( "Deux" ); } else if ( value == 3 ) { System.out.println( "Trois" ); } else if ( value == 4 ) { System.out.println( "Quatre" ); } else if ( value == 5 ) { System.out.println( "Cinq" ); } else if ( value == 6 ) { System.out.println( "Six" ); } else if ( value == 7 ) { System.out.println( "Sept" ); } else if ( value == 8 ) { System.out.println( "Huit" ); } else { System.out.println( "Neuf" ); } } } |
else
qui relance une condition.
Si on n'avait pas fait cela, les if
imbriqués auraient de plus en plus été décalés vers la droite.
Nous en avons déjà un peu parlé, vous avez la possibilité de générer un bloc if
, voir if
/else
via l'atelier Eclipse.
J'aimerais revenir quelques instants sur cette possibilité, car vous pouvez compléter ces possiblités.
Donc Eclipse, mais aussi certains autres IDE (Integrated development environment), fournit le concept de template (à ne pas confondre avec les templates C++). Un template est un modèle de code qui est enregistré dans l'IDE. Ce modèle de code est associé à un nom et il sera possible, durant l'édition de vos codes, d'utiliser ce nom pour y injecter la séquence de code associée. Pour réaliser cette injection, il faut taper le nom du template puis appuyer simultanément sur les touches CTRL + Espace : normalement, le nom du modèle (du template) doit être remplacé par le code Java équivalent.
Pour obtenir la boîte de dialogue de configuration des templates, ouvrez le menu « Window », puis activer l'élément de menu « Preferences ».
La boîte de dialogue d'édition des préférences s'ouvre : dans l'arborescence (dans la partie gauche), sélectionnez le noeud
« Java / Editor / Templates ». Vous pourrez y voir les templates déjà existant (syserr
, sysout
, main
,
if
, ifelse
, ...).
La capture d'écran ci-dessous vous montre les deux définitions de templates relatives à l'instruction if
.
Pour modifier le code d'un modèle existant, veuillez cliquer sur le bouton « Edit... » situé à droite de la boîte de dialogue.
Pour en ajouter un nouveau, veuillez cliquer sur le bouton « New... ». Notez aussi qu'un modèle ne contient pas que du texte brut.
Effectivement, dans la capture d'écran proposée, vous pouvez apercevoir deux variables dans le modèle et notamment la variable ${cursor}
.
Cette variable permet de préciser l'emplacement du curseur dans l'éditeur, une fois le modèle injecté : c'est très pratique.
Il existe de nombreuses autres variables utilisables dans un modèle de code Eclipse.
if
dont
la condition de test calcule une donnée entière. Cela ne marche pas en Java.
Le contrôle de typage du compilateur Java est plus strict que celui de C.
En Java, la condition du if
doit obligatoirement calculer une valeur booléenne.
Dans tout autre cas, une erreur de compilation sera générée.
L'objectif de ce TP est de produire un code permettant de déterminer si une année (saisie par l'utilisateur) est bissextile ou non. La difficulté réside dans l'algorithme à utiliser pour savoir si l'on a à faire à une année bissextile : voici cet algorithme.
Si l'année considérée n'est pas multiple de 4 alors elle n'est pas bissextile.
Si elle est alors il faut encore faire d'autres tests :
Si elle est multiple de 400 alors l'année est bissextile.
Si elle est multiple de 100 (mais pas de 400) alors elle n'est pas bissextile.
Dans les autres cas elle est bissextile.
Pour permettre la saisie d'une valeur entière, je vous propose le code suivant.
Nous reviendrons ultérieurement sur ce Scanner
et la construction try
(ici c'est plus précisément un « try-with-resources »),
pour l'heure acceptez simplement que la méthode nextInt
vous renvoie l'entier saisie sur la console.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
import java.util.Scanner; public class Demo { public static void main( String [] args ) { try ( Scanner scanner = new Scanner( System.in ) ) { System.out.print( "Veuillez saisir une année : " ); int year = scanner.nextInt(); // TODO: continuer le TP } } } |
Pour rappel, pour savoir si un entier est multiple d'une valeur, vous pouvez utiliser l'opérateur %
(le modulo, ou encore le reste de la
division entière). Par exemple 10 % 5
calcule 0 car 10 est divisible par 5 et que le reste de cette division entière est 0.
Voila, c'est maintenant à vous de me proposer une solution à ce problème. Jouez le jeu et ne passez pas directement à la correction ;-)
Je vous propose une première version ou nous allons simplement traduire l'énoncé en code Java.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
import java.util.Scanner; public class Demo { public static void main( String [] args ) { try ( Scanner scanner = new Scanner( System.in ) ) { System.out.print( "Veuillez saisir une année : " ); int year = scanner.nextInt(); boolean isBissextile; if ( year % 4 != 0 ) { isBissextile = false; // year n'est pas un multiple de 4 } else { if ( year % 400 == 0 ) { isBissextile = true; // year est multiple de 400 } else if ( year % 100 == 0 ) { isBissextile = false; // year est multiple de 100, mais pas de 400 (else) } else { isBissextile = true; } } if ( isBissextile ) { System.out.printf( "L'année %d est bissextile\n", year ); } else { System.out.printf( "L'année %d n'est pas bissextile\n", year ); } } } } |
Maintenant, notez qu'on aurait pu compacter les tests en utilisant les opérateurs &&
(et logique) et ||
(ou logique) :
cela permet de ne pas trop répéter les if
. Notez aussi que 400 et 100 sont des entiers multiples de 4. On peut donc réécrire le programme
ainsi :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
import java.util.Scanner; public class Demo { public static void main( String [] args ) { try ( Scanner scanner = new Scanner( System.in ) ) { System.out.print( "Veuillez saisir une année : " ); int year = scanner.nextInt(); if ( year % 400 == 0 || (year % 4 == 0 && year % 100 != 0) ) { System.out.printf( "L'année %d est bissextile\n", year ); } else { System.out.printf( "L'année %d n'est pas bissextile\n", year ); } } } } |
Tout comme l'instruction if
, le switch
permet de déclencher des traitements en fonction des valeurs d'une expression.
D'un certain point de vue, cette instruction est similaire à plusieurs if
imbriqués.
Néanmoins, il y a des différences notables entre les deux instructions :
La condition d'un if
doit calculer un booléen. Dans le cas d'un switch
, l'expression doit renvoyer soit un entier
(byte
, short
, int
, long
), soit un type énuméré, soit une chaîne de caractères ou encore
un caractère (ce qui revient plus ou moins au premier cas). Si l'expression calcule une valeur flottante (ou même une valeur booléenne), une
erreur de compilation sera produite.
Un switch
est plus optimisé qu'un ensemble de if
imbriqués. Effectivement, dans le cas d'un if
, et dans
le pire des cas, n
tests seront effectués (ou n
représente le nombre de if
imbriqués). Au contraire,
un switch
sur entier trouvera l'adresse en mémoire du code à exécuter pour chaque valeur en temps constant.
Comme nous venons de le dire, un switch
ne peut pas travailler sur des valeurs flottantes. Si vous tentez malgré tout de compiler un tel
programme, une erreur de compilation sera produite. Voici un exemple basé sur des valeurs de type double
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
public class Demo { public static void main( String [] args ) { double value = Math.random() * 10; switch ( value ) { case 1.0: System.out.println( "Ne peut pas compiler" ); break; default: System.out.println( "Autre valeur" ); } } } |
Et voici le message d'erreur produit lors d'une tentative de compilation.
$> javac Demo.java Demo.java:6: error: incompatible types: possible lossy conversion from double to int switch ( value ) { ^ 1 error $>
Regardons maintenant un exemple de code qui compile parfaitement : il s'agit d'un programme équivalent à celui vu précédemment avec l'instruction
if
pour afficher en toutes lettres un chiffre compris entre 0 et 9.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
public class Demo { public static void main( String [] args ) { int value = (int) (Math.random() * 11); switch( value ) { case 0: System.out.println( "Zéro" ); break; case 1: System.out.println( "Un" ); break; case 2: System.out.println( "Deux" ); break; case 3: System.out.println( "Trois" ); break; case 4: System.out.println( "Quatre" ); break; case 5: System.out.println( "Cinq" ); break; case 6: System.out.println( "Six" ); break; case 7: System.out.println( "Sept" ); break; case 8: System.out.println( "Huit" ); break; case 9: System.out.println( "Neuf" ); break; default: System.out.println( "Ce n'est plus un chiffre, mais un nombre" ); } } } |
Comme vous l'aurez constaté, chaque case
est associé à une valeur à tester et il se termine par une instruction break
.
Cette dernière instruction est très importante dans notre cas : elle permet que l'exécution ne se poursuive pas au case
suivant.
Effectivement, l'instruction switch
/case
est prévue pour définir des intervalles de valeurs, comme le montre l'exemple ci-dessous.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
public class Demo { public static void main( String [] args ) { int value = (int) (Math.random() * 11); switch( value ) { case 0: case 1: case 2: case 3: case 4: System.out.println( "Petit chiffre" ); break; case 5: case 6: case 7: case 8: case 9: System.out.println( "Grand chiffre" ); break; default: System.out.println( "Ce n'est plus un chiffre, mais un nombre" ); } } } |
Cet exemple définit trois cas possibles d'exécution :
la valeur est comprise entre 0 et 4 : dans ce cas on rentre dans le case
associé et comme il n'y a pas de break
,
on passe dans les cases
suivants, jusqu'à arriver au case 4
, ce qui affiche "Petit chiffre"
.
Les différents blocs case
s'enchainent dans l'ordre de définition.
la valeur est comprise entre 5 et 9 : Cela marche à peu près de la même manière que les cas précédents. On finit donc par tomber dans le
case 9
qui affiche "Grand chiffre"
.
la valeur est supérieure ou égale à 10 : on rentre alors dans le bloc default
qui déclenche pour tous les autres cas.
N'oubliez donc pas vos break
si vous ne souhaitez pas poursuivre l'exécution du code au bloc suivant.
Petit complément d'information pour les utilisateurs de l'IDE Eclipse (et de quelques autres IDE) : tout comme pour l'instruction if
,
il existe un template permettant de générer un squelette d'instruction switch
. Pour obtenir cette possibilité, commencez à tapez le mot
switch
à l'endroit souhaité puis, encore une fois, appuyez simultanément sur CTRL et ESPACE : vous devriez pouvoir activer le template
et générer la séquence de code souhaitée.
Comme nous l'avons dit en introduction, comparé à un l'instruction if
, l'instruction switch
est optimisée. Dans les exemples de
transformation d'une valeur numérique entière en son équivalent textuel, un if
peut réaliser un grand nombre de tests avant de trouver le bon
bloc à exécuter. Au contraire un switch
trouveras le bon bloc en temps constant et ce quelle que soit la valeur numérique entière considérée.
Pour effectuer ce tour de force, le compilateur Java pré-calcule un tableau contenant les adresses mémoire des codes à exécuter pour chaque valeur possible. Ainsi, durant l'exécution, il suffira de retrouver l'entrée du tableau correspond à la valeur considérée (déterminée en temps constant) puis de sauter à l'adresse mémoire stockée dans cette entrée. Considérons l'exemple de code ci-dessous : l'image, proposée sur votre droite, montre comment les choses seront organisées en mémoire.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
switch( index ) { case 0: // code branche 0; break; case 1: // code branche 1; break; case 3: // code branche 3; break; default: // code branche default; } |
Si vous regardez bien l'exemple de code, vous noterez qu'il n'y a pas de case
pour la valeur 2
. Pour autant le tableau de
pointeurs possède bien une entrée pour cette valeur et le pointeur considéré renvoie sur le bloc default
. Pour les valeurs en dehors des
bornes, deux tests préalables sont réalisés et si on est bien en dehors des bornes du tableau, on renvoie aussi sur default
.
Imaginons un autre cas : les valeurs à tester sont, par exemple, 100
, 101
, 102
, 103
, 104
et 105
. Dans ce cas, le compilateur calcule la valeur minimale (soit 100
) et la retire de la donnée passée en entrée du
switch
. Ainsi, on se retrouve bien avec un tableau de pointeurs indicé de 0 à 5 : l'optimisation est bien mise en oeuvre.
break
dans une branche, alors l'exécution se poursuit au bloc
suivant. Les blocs s'enchaînent en mémoire dans le même ordre que celui qui a permis de les définir dans le code.
Enfin, il faut savoir que cette optimisation ne peut pas être utilisée pour des switch
sur chaînes de caractères ou sur des types énumérés.
Pour autant d'autres optimisations, adaptées à ces types, sont mises en oeuvre.
Comme l'on vient de le préciser, on peut appliquer un switch
sur des chaînes de caractères. Il faut néanmoins noter que l'on peut
utiliser cette possibilité que depuis la version Java SE 7.0. Avant, une erreur de compilation était produite. Si vous êtes dans ce cas, préférez
l'utilisation de if
imbriqués.
Voici un exemple d'utilisation d'un switch
sur des chaînes de caractères : il permet de simuler un petit interpréteur de commandes.
Tapez exit
pour sortir de ce mini-shell.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
import java.util.Scanner; public class Demo { public static void main( String [] args ) throws Exception { try ( Scanner scanner = new Scanner( System.in ) ) { while( true ) { System.out.print( "Enter a command: "); String command = scanner.nextLine().trim(); switch( command ) { case "exit": System.out.println( "Bye bye" ); return; case "command1": System.out.println( "Imagine a command for your system" ); break; case "command2": System.out.println( "Imagine another command for your system" ); break; default: System.out.println( "Command not found. Please retry!" ); } } } } } |
try
nous permet, dans ce cas d'utilisation, fermer automatiquement l'instance scanner
(via un appel de la méthode
close
) en sortie du programme. La sortie du programme s'obtient lors de l'exécution du return
dans le bloc case "exit"
.
Nous reviendrons ultérieurement sur le fonctionnement de cette instruction. De même, nous reviendrons ultérieurement sur l'utilisation du mot clé
throws
(à la fin du main
).
Le but de ce TP est d'écrire un programme permettant d'appliquer une opération sur deux valeurs numériques. L'opérateur ainsi que les valeurs doivent
être saisis à partir de la console en suivant le format suivant : java Demo operator value1 value2
. Un minimum de quatre opérateurs
doivent être fournis : add, sub, mul et div. Voici un petit exemple d'utilisation du programme à développer.
$> java Demo Usage: java Demo operator value1 value2 $> java Demo add 10.2 3.4 13.6 $> java Demo sub 10.2 3.4 6.8 $> java Demo mul 5 3 15.0 $> java Demo div 10 5 2.0 $>
Voila, c'est maintenant à vous de me proposer une solution à ce problème. Jouez le jeu et ne passez pas directement à la correction ;-)
La correction
Pour choisir l'opération à exécuter en fonction de l'opérateur renseigné, je vous conseille d'utiliser l'instruction switch
afin
d'avoir un programme plus lisible et efficace. Voici ma correction.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
public class Demo { public static void main( String [] args ) { if ( args.length != 3 ) { System.out.println( "Usage: java Demo operator value1 value2" ); System.exit( 0 ); } String operatorName = args[0]; double value1 = Double.parseDouble( args[1] ); double value2 = Double.parseDouble( args[2] ); switch( operatorName ) { case "add": System.out.println( value1 + value2 ); break; case "sub": System.out.println( value1 - value2 ); break; case "mul": System.out.println( value1 * value2 ); break; case "div": System.out.println( value1 / value2 ); break; default: System.out.println( "Undefined operator " + operatorName ); } } } |
Quand à parler d'exécutions conditionnelles, je souhaitais aussi présenter l'opérateur conditionnel. Certes, ce n'es pas une instruction, mais bien
un opérateur (qui calcule une valeur), mais son fonctionnement est assez proche d'un if
/else
. Le format d'utilisation de cet
opérateur est le suivant :
booleanExpression ? trueExpression : falseExpression
Donc, pour utiliser l'opérateur conditionnel, il faut fournir trois opérandes. Le premier correspond à l'expression de test : cette expression doit
renvoyer une valeur booléenne. Le second opérande, séparé du premier par un caractère ?
, correspond à la valeur à calculer dans le cas
où l'expression initiale est vraie. Le troisième opérande, séparé du second par le caractère :
, correspond à la valeur à calculer dans le
cas où l'expression initiale est fausse. Voici un petit exemple d'utilisation.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
import java.util.Scanner; public class Demo { public static void main( String [] args ) { try ( Scanner scanner = new Scanner( System.in ) ) { System.out.print( "Please enter a password: " ); String password = scanner.nextLine(); String message = password.equals( "007" ) ? "You are connected" : "You are not connected"; System.out.println( message ) ; } } } |
message
).
Il est donc important que les deux expressions à la droite du caractère ?
calculent des chaînes de caractères, ce qui est notre cas.
Si vous changez l'une des deux expressions de droite afin qu'elle renvoie un entier, voici l'erreur de compilation qui sera produit.
$> javac Demo.java Demo.java:12: error: incompatible types: bad type in conditional expression String message = password.equals( "007" ) ? "You are connected" : 10; ^ int cannot be converted to String 1 error $>
Ecrire un programme qui calcul le minimum de deux valeurs qui sont passées en arguments du main
,
en utilisant l'opérateur conditionnel. Jouez le jeu et ne passez pas directement à la correction ;-)
A mon sens, la première des choses à faire est de transformer vos arguments en valeurs numériques : n'oubliez pas que args
et un
tableau de chaînes de caractères. Ensuite, comparez les deux valeurs avec l'opérateur conditionnel pour trouver le plus petit des deux.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
public class Demo { public static void main( String [] args ) { if ( args.length != 2 ) { System.out.println( "Usage: java Demo value1 value2" ); System.exit( 0 ); } double value1 = Double.parseDouble( args[0] ); double value2 = Double.parseDouble( args[1] ); double min = value1 < value2 ? value1 : value2; System.out.println( "Le minimum est " + min ); } } |
Améliorations / Corrections
Vous avez des améliorations (ou des corrections) à proposer pour ce document : je vous remerçie par avance de m'en faire part, cela m'aide à améliorer le site.
Emplacement :
Description des améliorations :