Comment fonctionne la classe Java Scanner en interne
La classe java.util.Scanner est l'outil le plus courant pour lire une entrée texte en Java. Derrière sa simplicité apparente se cache un moteur de parsing basé sur les expressions régulières qui explique à la fois sa flexibilité et ses performances modestes.
Utilisation basique
import java.util.Scanner;
Scanner sc = new Scanner(System.in);
System.out.print("Votre nom : ");
String nom = sc.nextLine();
System.out.print("Votre âge : ");
int age = sc.nextInt();
System.out.println("Bonjour " + nom + ", " + age + " ans");
sc.close();
Le fonctionnement interne
Scanner est conçu autour de trois éléments clés :
- Un
Readablesource (InputStream,File,String,ReadableByteChannel…). Scanner le convertit enCharBufferinterne pour éviter des appels I/O excessifs. - Un
Patternséparateur (par défaut\\p{javaWhitespace}+). Il définit ce qui sépare les tokens. - Un tampon et un
Matcher. À chaque appel denext(),nextInt(), etc., Scanner avance dans le buffer en cherchant le prochain motif correspondant au type demandé.
Le rôle du séparateur
Le séparateur est une expression régulière qu'on peut redéfinir :
Scanner sc = new Scanner("rouge,vert,bleu");
sc.useDelimiter(",");
while (sc.hasNext()) {
System.out.println(sc.next()); // rouge, puis vert, puis bleu
}
En combinant useDelimiter() et des regex, on peut parser des formats complexes sans tokenisation manuelle.
Les méthodes de lecture
| Méthode | Lit | Regex utilisée (implicite) |
|---|---|---|
next() | Un token (défini par le séparateur) | — |
nextLine() | Toute la ligne jusqu'au saut | jusqu'Ă \n |
nextInt() | Un entier | -?\d+ |
nextDouble() | Un décimal | motif numérique localisé |
nextBoolean() | true ou false | (true|false) |
Chaque nextXxx() applique le pattern au buffer ; si la correspondance échoue, InputMismatchException est levée, mais le buffer n'est pas consommé — vous pouvez appeler next() pour sauter le token invalide.
Le piège classique : next() puis nextLine()
System.out.print("Âge : ");
int age = sc.nextInt(); // lit 30, laisse \n dans le buffer
System.out.print("Nom : ");
String nom = sc.nextLine(); // lit la ligne vide restée dans le buffer !
Solution : ajouter un sc.nextLine() après le nextInt() pour consommer le saut de ligne, ou parser tout en nextLine() puis convertir.
Dépendance au Locale
Scanner respecte le Locale courant pour les formats numériques. Sur un système français, "3,14" est accepté ; sur un système US, "3.14". Cela peut surprendre en production :
Scanner sc = new Scanner("3.14");
sc.useLocale(java.util.Locale.US);
double pi = sc.nextDouble(); // 3.14
Performance
Scanner est lent comparé à BufferedReader : la compilation et l'application d'un Pattern à chaque token coûte ~10 à 50× plus. Sur des concours de programmation ou du traitement de très gros fichiers, vous verrez souvent :
import java.io.*;
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String ligne;
while ((ligne = br.readLine()) != null) {
String[] parts = ligne.split("\\s+");
int a = Integer.parseInt(parts[0]);
// ...
}
Moins élégant, mais 10 à 50 fois plus rapide. Pour un traitement encore plus fin, StreamTokenizer permet un contrôle bas niveau sans regex.
Ne pas fermer System.in
Quand la source est System.in, ne fermez pas le Scanner — ou fermez-le en fin de programme seulement. Fermer un Scanner ferme sa source sous-jacente, et System.in fermé ne se rouvre pas.
Résumé
- Scanner =
Readable+Patternséparateur +Matcher. - Excellent pour des lectures simples, des scripts, des TP.
- Éviter sur les gros volumes : préférer
BufferedReader. - Attention au mélange
nextInt()/nextLine()et au Locale. - Toujours
close()pour les Scanner ouverts sur unFile, jamais surSystem.inen cours de programme.
Connaître le moteur interne vous aide à choisir Scanner quand c'est pertinent, et à vous en passer quand la performance l'exige.