La classe java.util.Scanner est un outil fondamental en Java pour lire des données primitives et des chaînes de caractères depuis diverses sources, telles que la console. Bien qu'elle soit intuitive pour des lectures simples, des comportements inattendus peuvent survenir lors de l'utilisation combinée de certaines de ses méthodes, notamment nextInt(), next() et nextLine().
Le piège de nextInt() suivi de nextLine()
Un problème courant se manifeste lorsque l'on tente de lire un nombre entier à l'aide de nextInt(), suivi immédiatement d'une tentative de lecture d'une ligne entière avec nextLine(). L'exécution de nextLine() après nextInt() semble souvent ignorer l'entrée de l'utilisateur pour la chaîne de caractères, renvoyant une chaîne vide. Cela est dû à la manière dont ces méthodes gèrent le flux d'entrée et les caractères de fin de ligne.
Démonstration du problème
Considérons l'exemple de code suivant :
import java.util.Scanner;
public class GestionnaireEntreeProbleme {
public static void main(String[] args) {
Scanner scannerPrincipal = new Scanner(System.in);
System.out.println("Veuillez saisir un nombre entier :");
while (scannerPrincipal.hasNextInt()) {
int valeurEntiere = scannerPrincipal.nextInt(); // Lit l'entier
System.out.println("Veuillez saisir une chaîne de caractères (qui sera ignorée) :");
String chaineLue = scannerPrincipal.nextLine(); // Tente de lire la ligne suivante
System.out.println("Résultat: Entier=" + valeurEntiere + ", Chaîne='" + chaineLue + "'");
System.out.println("\nVeuillez saisir un autre nombre entier ou 'q' pour quitter :");
}
scannerPrincipal.close();
}
}
Lors de l'exécution de ce code, après avoir entré un entier et appuyé sur Entrée, le programme ne vous donnera pas la possibilité de saisir la chaîne de caractères. La sortie affichera une chaîne vide pour chaineLue, comme ceci :
Veuillez saisir un nombre entier :
123
Veuillez saisir une chaîne de caractères (qui sera ignorée) :
Résultat: Entier=123, Chaîne=''
Veuillez saisir un autre nombre entier ou 'q' pour quitter :
Explication du comportmeent
nextInt()(ounextBoolean(),nextDouble(), etc.) lit le prochain jeton de l'entrée qui correspond au type attendu. Il s'arrête lorsqu'il rencontre un délimiteur (par défaut, un espace blanc, y compris le caractère de nouvelle ligne\n). Crucialement, il ne consomme pas ce délimiteur final.- Lorsque vous saisissez un nombre comme "123" et appuyez sur Entrée, la séquence d'entrée est "123\n".
nextInt()lit "123" et laisse le caractère de nouvelle ligne\ndans le tampon d'entrée. - Ensuite, lorsque
nextLine()est appelé, il lit tout ce qui reste dans le tampon jusqu'à (et y compris) le prochain caractère de nouvelle ligne. Comme le\nest déjà présent,nextLine()le consomme immédiatement, considérant qu'il a lu une ligne vide, et renvoie une chaîne vide.
Comprendre les méthodes de Scanner
next(),nextInt(),nextDouble(), etc. : Ces méthodes lisent des "jetons" (séquences de caractères non blancs) jusqu'à ce qu'elles rencontrent un délimiteur (espace, tabulation, nouvelle ligne). Elles ne consomment pas le délimiteur qui marque la fin du jeton.nextLine(): Cette méthode lit toute l'entrée jusqu'à ce qu'elle rencontre un caractère de nouvelle ligne (\n). Elle consomme et ignore ce caractère de nouvelle ligne, déplaçant le curseur au début de la ligne suivante.
Solutions au problème
1. Consommer explicitement le caractère de nouvelle ligne en attente
La solution la plus courante et la plus robuste consiste à ajouter un appel "factice" à nextLine() après toute méthode next*() (sauf nextLine() elle-même) pour vider le reste de la ligne, y compris le caractère de nouvelle ligne. Cela assure que le prochain appel à nextLine() attendra une nouvelle entrée.
import java.util.Scanner;
public class GestionnaireEntreeCorrige {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.println("Veuillez entrer votre âge (entier) :");
while (sc.hasNextInt()) {
int ageUtilisateur = sc.nextInt(); // Lit l'entier
sc.nextLine(); // CONSOMME le caractère de nouvelle ligne restant
System.out.println("Veuillez entrer votre nom complet :");
String nomCompletUtilisateur = sc.nextLine(); // Lit maintenant correctement la ligne du nom
System.out.println("Bonjour " + nomCompletUtilisateur + ", vous avez " + ageUtilisateur + " ans.");
System.out.println("\nVeuillez entrer un autre âge ou 'q' pour quitter :");
}
sc.close();
}
}
Avec cette correction, le programme se comporte comme prévu :
Veuillez entrer votre âge (entier) :
30
Veuillez entrer votre nom complet :
Jean Dupont
Bonjour Jean Dupont, vous avez 30 ans.
Veuillez entrer un autre âge ou 'q' pour quitter :
2. Utiliser next() pour les chaînes de caractères (avec précautions)
Si vous êtes certain que la chaîne de caractères à lire ne contiendra pas d'espaces et peut être traitée comme un jeton unique, vous pourriez utiliser next() au lieu de nextLine(). Cependant, next() ne lit qu'un seul mot (jusqu'au premier espace blanc), ce qui n'est pas toujours le comportement désiré pour une "ligne" de texte.
import java.util.Scanner;
public class GestionnaireEntreeNextString {
public static void main(String[] args) {
Scanner lecteurConsole = new Scanner(System.in);
System.out.println("Saisissez un numéro de pièce :");
while (lecteurConsole.hasNextInt()) {
int numeroPiece = lecteurConsole.nextInt(); // Lit le nombre
System.out.println("Saisissez le nom du fabricant (un seul mot) :");
String nomFabricant = lecteurConsole.next(); // Lit le prochain jeton (mot)
System.out.println("Pièce #" + numeroPiece + " fabriquée par " + nomFabricant + ".");
System.out.println("\nSaisissez un autre numéro de pièce ou 'q' pour quitter :");
}
lecteurConsole.close();
}
}
Cette approche fonctionne pour des mots uniques, mais échouera si l'utilisateur entre une phrase contenant des espaces. Pour la saisie de phrases, la solution du nextLine() factice reste la plus appropriée et la plus sûre.