Le If en Java

Dans cet article, nous vous présentons la prise de décision en Java à travers des exemples concrets et des explications détaillées. Vous découvrirez comment utiliser les instructions if et else, ainsi que la nouvelle fonctionnalité de correspondance de motifs, afin de créer des applications modulables et adaptées à divers scénarios réels. De plus, cet exposé de prise de décision en Java met l’accent sur des exemples concrets, comme la gestion d’un zoo qui rappelle aux visiteurs d’apporter un parapluie lors d’une journée pluvieuse ou de fermer en cas de neige.

Instructions et Blocs – Fondamentaux de la prise de décision en Java

Comme vous vous en souvenez peut-être du Chapitre 1, « Les blocs de construction », une instruction Java est une unité complète d’exécution, terminée par un point-virgule (;). Dans ce chapitre, nous vous présentons diverses instructions de contrôle de flux qui permettent d’orienter la prise de décision en Java. En effet, ces instructions interrompent le déroulement du programme grâce à des décisions, des boucles et des branchements, permettant ainsi à l’application d’exécuter sélectivement certains segments de code.

Ces instructions peuvent s’appliquer à une expression unique ou à un bloc complet de code. Un bloc de code en Java est un ensemble de zéro ou plusieurs instructions, délimité par des accolades ({ }), et peut être utilisé partout où une seule instruction est autorisée. Par exemple, les deux extraits suivants sont équivalents :


// Instruction unique 
visiteurs++;

// Instruction dans un bloc 
{ 
  visiteurs++; 
}
  

Une instruction ou un bloc sert souvent de cible à une instruction conditionnelle. Ainsi, nous pouvons préfixer l’instruction if aux exemples suivants :


// Instruction unique 
if(billetsPris > 1) 
  visiteurs++;

// Instruction dans un bloc 
if(billetsPris > 1) { 
  visiteurs++; 
}
  

Bien que les deux exemples soient équivalents, il est souvent préférable, d’un point de vue stylistique, d’utiliser des blocs. En outre, cette méthode facilite l’ajout de nouvelles instructions sans modifier la structure globale.

Utilisation de l’instruction if pour la prise de décision en Java

Souvent, nous souhaitons exécuter un bloc de code uniquement dans certaines circonstances. L’instruction if, illustrée dans la Figure 3.1 ci-dessous, permet à notre application d’exécuter un segment spécifique de code si une expression booléenne s’évalue à vrai.

StructureDescription
if(condition)Exécute l’instruction suivante si la condition est vraie

Par exemple, imaginez une fonction qui utilise l’heure de la journée, une valeur entière de 0 à 23, pour afficher un message à l’utilisateur :


if(heureDuJour < 11) 
  System.out.println("Bonjour");
  

Si l’heure est inférieure à 11, le message s’affiche. Supposons maintenant que nous souhaitions également incrémenter une variable compteurDeSalutationsMatinales chaque fois que la salutation est affichée. Au lieu de dupliquer l’instruction if, nous pouvons utiliser un bloc :


if(heureDuJour < 11) {
  System.out.println("Bonjour");
  compteurDeSalutationsMatinales++;
}
  

Par ailleurs, il est important de noter que l’indentation peut parfois être trompeuse. Considérez l’exemple suivant :

if(heureDuJour < 11)
  System.out.println("Bonjour");
  compteurDeSalutationsMatinales++;

D’après l’indentation, on pourrait penser que la variable compteurDeSalutationsMatinales ne s’incrémente que si l’heure est inférieure à 11. Cependant, seule l’instruction d’affichage dépend de la condition, tandis que l’incrémentation se fait inconditionnellement. Ainsi, il est essentiel de suivre les accolades plutôt que l’indentation, car en Java, les tabulations ne sont que des espaces.

Application de l’instruction else dans la prise de décision en Java

Élargissons notre exemple. Que faire si nous souhaitons afficher un message différent lorsque l’heure atteint 11 ou plus ? Par exemple :


if(heureDuJour < 11) {
  System.out.println("Bonjour");
}

if(heureDuJour >= 11) {
  System.out.println("Bon Après-midi");
}
  

Cette solution est fonctionnelle, mais elle évalue la condition sur heureDuJour à deux reprises, ce qui est redondant. Heureusement, Java offre l’instruction else pour simplifier ce cas :

StructureDescription
if(condition) { ... } else { ... }Exécute l’un des deux blocs en fonction de la condition

Revenons à cet exemple :


if(heureDuJour < 11) {
  System.out.println("Bonjour");
} else 
  System.out.println("Bon Après-midi");
  

De plus, il est possible d’enchaîner plusieurs conditions pour obtenir un comportement plus précis :


if(heureDuJour < 11) {
  System.out.println("Bonjour");
} else if(heureDuJour < 15) {
  System.out.println("Bon Après-midi");
} else {
  System.out.println("Bonsoir");
}
  

Vérification des expressions booléennes dans l’instruction if

Un écueil courant consiste à utiliser une expression qui n’est pas de type booléen dans l’instruction if. Par exemple :


int heureDuJour = 1;

if(heureDuJour) { // NE COMPILE PAS
  …
}
  

Raccourcir le code avec la correspondance de motifs

Depuis Java 16, la correspondance de motifs avec l’instruction if et l’opérateur instanceof simplifie le contrôle du flux d’exécution. Cette technique permet d’exécuter un segment de code uniquement lorsque certains critères sont remplis. Ainsi, elle contribue à réduire le code répétitif, souvent présent dans de nombreuses applications Java.

Il est important de ne pas confondre cette fonctionnalité avec la classe Pattern ou les expressions régulières, même si ces dernières peuvent être utilisées pour le filtrage.

Exemple de correspondance de motifs : Comparer des entiers


void comparerEntiers(Number nombre) {
  if(nombre instanceof Integer) {
    Integer donnee = (Integer)nombre;
    System.out.print(donnee.compareTo(5));
  }
}
  

De plus, grâce à la correspondance de motifs, nous pouvons écrire le code de façon plus concise :


void comparerEntiers(Number nombre) {
  if(nombre instanceof Integer donnee) {
    System.out.print(donnee.compareTo(5));
  }
}
  

Réaffectation des variables de motif

Bien que cela soit possible, il est déconseillé de réaffecter une variable de motif, car cela peut créer une ambiguïté quant à son champ de visibilité.


if(nombre instanceof Integer donnee) {
  donnee = 10;
}
  

La réaffectation peut être empêchée en utilisant le modificateur final, bien qu’il soit préférable de ne pas modifier la variable du tout :


if(nombre instanceof final Integer donnee) {
  donnee = 10; // NE COMPILE PAS
}
  

Variables de motif et expressions

La correspondance de motifs permet également d’utiliser des expressions pour filtrer les données. Par exemple :


void afficherEntiersSuperieursA5(Number nombre) {
  if(nombre instanceof Integer donnee && donnee.compareTo(5) > 0)
    System.out.print(donnee);
}
  

Sous-types et limitations dans la correspondance de motifs

Le type de la variable de motif doit être un sous-type de la variable à gauche de l’expression et ne peut pas être identique. Cette règle ne s’applique pas aux expressions traditionnelles avec instanceof. Considérez l’exemple suivant :


Integer valeur = 123;
if(valeur instanceof Integer) {}
if(valeur instanceof Integer donnee) {} // NE COMPILE PAS
  

Par ailleurs, le compilateur présente certaines limites lorsqu’on mélange classes et interfaces. Par exemple, avec la classe non finale Number et l’interface List, le code compile même s’ils ne sont pas liés :


Number valeur = 123;
if(valeur instanceof List) {}
if(valeur instanceof List donnee) {}
  

Portée de flux dans la correspondance de motifs

Le compilateur applique une portée de flux lorsque l’on utilise la correspondance de motifs. Cela signifie que la variable n’est accessible que lorsque le compilateur peut déterminer de manière définitive son type. En outre, cette portée est déterminée par les branchements du programme et diffère des portées d’instance, de classe ou locales.

Par conséquent, comprenez bien pourquoi le code suivant ne compile pas :


void afficherEntiersOuNombresSuperieursA5(Number nombre) {
  if(nombre instanceof Integer donnee || donnee.compareTo(5) > 0)
    System.out.print(donnee);
}
  

En effet, si l’entrée n’est pas une instance d’Integer, la variable donnee n’est pas définie. Ainsi, le compilateur ne peut garantir qu’elle appartient au bon type, et le code ne compile pas.

Exercice pratique : Comprendre la portée des variables de motif

Qu’en est-il de cet exemple ?


void afficherEntierDeuxFois(Number nombre) {
  if (nombre instanceof Integer donnee)
    System.out.print(donnee.intValue());
  System.out.print(donnee.intValue()); // NE COMPILE PAS
}
  

Puisque l’entrée pourrait ne pas être une instance d’Integer, la variable donnee n’est plus dans le champ de visibilité après l’instruction if. Cependant, considérez l’exemple suivant qui compile correctement :


void afficherUniquementEntiers(Number nombre) {
  if (!(nombre instanceof Integer donnee)) 
    return;
  System.out.print(donnee.intValue());
}
  

De plus, une autre approche consiste à utiliser une branche else :


void afficherUniquementEntiers(Number nombre) {
  if (!(nombre instanceof Integer donnee)) 
    return;
  else 
    System.out.print(donnee.intValue());
}
  

Nous pouvons également inverser les branches en inversant l’expression booléenne :

void afficherUniquementEntiers(Number nombre) {
  if (nombre instanceof Integer donnee) 
    System.out.print(donnee.intValue());
  else 
    return;
}  

Ainsi, notre nouveau code est équivalent à l’original et démontre clairement comment le compilateur détermine que la variable donnee est accessible uniquement lorsque nombre est bien une instance d’Integer.

En conclusion, la maîtrise de la prise de décision en Java grâce aux instructions if, else et à la correspondance de motifs vous permettra d’écrire un code plus clair, efficace et moins répétitif. Par ailleurs, ces techniques sont essentielles pour développer des applications réactives qui répondent aux scénarios du monde réel, comme par exemple dans la gestion d’un zoo par Tanguy Le Loch, où chaque décision influence directement le comportement du logiciel.