W3docs

La classe Java Scanner

Analysez des primitives et des chaînes depuis une entrée texte en Java avec la classe Scanner — nextInt, nextLine, useDelimiter.

BufferedReader.readLine() du chapitre sur les flux mis en tampon est l'outil approprié lorsque l'entrée est orientée ligne et que vous souhaitez chaque ligne sous forme de String. Scanner est l'outil approprié lorsque l'entrée est un flux de tokens — entiers, doubles, mots séparés par des espaces, ou champs séparés par une expression régulière de votre choix. C'est l'analyseur livré intégré dans le JDK.

Scanner est également la classe que la plupart des tutoriels Java d'introduction utilisent pour lire depuis le clavier. new Scanner(System.in) et vous disposez d'un programme interactif fonctionnel en deux lignes. Cette commodité s'accompagne d'un piège bien connu — le piège nextInt/nextLine — dont ce chapitre traite principalement.

Ce que Scanner analyse

Les méthodes de lecture de tokens, associées à leurs prédicats hasNext :

boolean hasNext();        String  next();           // a whitespace-delimited token
boolean hasNextInt();     int     nextInt();        // a token parsed as int
boolean hasNextLong();    long    nextLong();
boolean hasNextDouble();  double  nextDouble();
boolean hasNextBoolean(); boolean nextBoolean();
boolean hasNextLine();    String  nextLine();       // the rest of the current line

Le contrat est identique pour toutes les méthodes typées : hasNextX() vérifie si le prochain token peut être analysé en tant que X sans le consommer ; nextX() le consomme. Un désaccord (nextInt() alors que le token est "hello") lève une InputMismatchException. La fin du flux lève une NoSuchElementException.

Un token est, par défaut, une suite maximale de caractères non blancs. Le motif délimiteur est ce que Pattern.UNICODE_CHARACTER_CLASS considère comme un espace blanc — espaces, tabulations, sauts de ligne et similaires. Vous pouvez le modifier avec useDelimiter(...).

Constructeurs

new Scanner(InputStream source);                     // typical: System.in
new Scanner(InputStream source, Charset charset);    // explicit charset (preferred for files)
new Scanner(Path source, Charset charset);           // open a file by path
new Scanner(String source);                          // parse a literal String — great for tests
new Scanner(Readable source);                        // wrap any Readable (Reader, CharBuffer, ...)

Même règle que pour le reste de java.io/java.nio : toujours passer un charset explicite lors de la lecture d'octets. Les constructeurs sans charset utilisent par défaut l'encodage de la plateforme.

try (Scanner s = new Scanner(path, StandardCharsets.UTF_8)) {
  while (s.hasNextInt()) {
    process(s.nextInt());
  }
}

Fermer le Scanner ferme le flux sous-jacent. Ne pas fermer un Scanner encapsulant System.in — le fermer ferme System.in, et toute lecture ultérieure dans la même JVM échouera.

Le piège nextInt / nextLine

La question Java la plus posée sur Stack Overflow.

Scanner s = new Scanner(System.in);
System.out.print("age: ");  int age = s.nextInt();
System.out.print("name: "); String name = s.nextLine();

Tapez 30, appuyez sur Entrée, puis Alice, appuyez sur Entrée. Attendu : age=30, name=Alice. Résultat réel : age=30, name="".

La raison : nextInt() lit les chiffres 30 et s'arrête. Il laisse le \n de fin dans le tampon d'entrée. Le prochain nextLine() lit tout jusqu'au prochain saut de ligne — qui est là, immédiatement — et retourne la chaîne vide avant que l'utilisateur ait eu la chance de taper quoi que ce soit.

La correction est l'une des suivantes :

int age = s.nextInt(); s.nextLine();                 // explicit "skip to end of line"
String name = s.nextLine();

ou, de manière plus robuste, analysez la ligne entière vous-même :

int age = Integer.parseInt(s.nextLine().trim());     // always reads the full line
String name = s.nextLine();

Le deuxième motif est celui que j'utilise dans du vrai code. Mélanger les méthodes de lecture de tokens (nextInt, nextDouble, next) avec la lecture de lignes (nextLine) est une recette pour des bugs de décalage ; choisissez-en une et tenez-vous-y. Soit analysez ligne par ligne avec nextLine, soit analysez token par token avec next* et n'appelez nextLine que dans le but explicite de « passer à la fin de cette ligne ».

hasNext est la condition de boucle

La forme de toute boucle Scanner :

while (s.hasNextInt()) {                             // predicate, no exception
  int n = s.nextInt();                               // consume
  process(n);
}

hasNextInt() retourne false en fin de flux et lorsque le prochain token n'est pas un entier — la boucle se termine donc proprement sur EOF et sur un token non numérique (ce qui est souvent la bonne chose, par exemple lorsque le pied de page est non numérique). Si vous voulez plutôt échouer bruyamment, utilisez hasNext() et laissez nextInt() lever InputMismatchException en cas de désaccord :

while (s.hasNext()) {
  int n = s.nextInt();                               // throws if the token isn't an int
  process(n);
}

Même vérification de fin de flux, comportement différent sur les tokens incorrects.

Délimiteurs personnalisés

Le délimiteur par défaut est l'espace blanc. Pour une entrée de type CSV, vous pouvez le modifier :

s.useDelimiter(",|\\R");                             // comma or any line break

\\R est l'expression régulière Java pour « toute séquence de saut de ligne » (\n, \r\n, \r, plus les séparateurs de ligne Unicode). Le motif combiné divise sur les virgules et les sauts de ligne, donc 1,2,3\n4,5,6 produit six tokens.

Cela dit : pour du vrai CSV, utilisez une bibliothèque CSV. Scanner ne gère pas les champs entre guillemets, les virgules échappées ou les sauts de ligne intégrés. Pour les cas simples — une liste de nombres, une configuration délimitée par des espaces — c'est parfait.

Le piège des paramètres régionaux

nextDouble() analyse avec le séparateur décimal du paramètre régional par défaut. Sur une JVM allemande, 3.14 échoue (3,14 est la forme allemande). Sur une JVM américaine, 3,14 échoue.

Pour une entrée lisible par machine, forcez le paramètre régional de l'analyseur :

s.useLocale(Locale.ROOT);                            // dot as decimal separator, no grouping
double x = s.nextDouble();                           // now parses "3.14"

Locale.ROOT est le paramètre régional « neutre » — la convention pour analyser des fichiers de données qui ne sont pas destinés aux humains. Oublier cela est la raison la plus courante pour qu'un lecteur CSV fonctionne en développement et échoue en CI : la machine du développeur et la machine CI ont des paramètres régionaux par défaut différents.

Scanner vs BufferedReader

ScannerBufferedReader
Litdes tokens (typés)des lignes (String)
Vitesselent (regex sur chaque token)rapide
Commoditéélevée (nextInt etc.)faible (vous analysez vous-même)
Idéal pourpetites entrées, invites interactives, testsgrands fichiers, traitement de logs, boucles intensives

Règle générale : si l'entrée provient d'un humain et que vous voulez des types, utilisez Scanner. Si l'entrée est un fichier et que vous voulez des lignes, utilisez BufferedReader. Pour des entrées de taille compétitive (millions de tokens), BufferedReader + StringTokenizer est d'un ordre de grandeur plus rapide que Scanner.

Un exemple concret : analyser un petit format texte

Le programme ci-dessous analyse un petit fichier texte délimité par des espaces avec trois enregistrements par ligne — id name score — en utilisant Scanner. Il illustre la boucle hasNextInt(), la correction des paramètres régionaux pour nextDouble(), le piège nextInt/nextLine et sa résolution, et enfin useDelimiter pour une alternative de type CSV.

java— editable, runs on the server

Ce qu'il faut retenir de l'exécution :

  • La première lecture a analysé trois enregistrements de trois types différents en trois lignes de code. L'API basée sur les tokens est véritablement pratique lorsque l'entrée est structurée en tokens — pas de regex, pas de String.split, pas de Integer.parseInt manuel. C'est le cas d'usage pour Scanner.
  • useLocale(Locale.ROOT) était la ligne qui a rendu 97.5 analysable. Sans elle, l'analyseur utilise le paramètre régional par défaut de la JVM ; sur une machine où c'est l'allemand, 97.5 lèverait InputMismatchException. Pour une entrée lisible par machine, épinglez toujours le paramètre régional.
  • La séparation buggy/corrigée pour le piège a affiché name='' puis name='Alice'. Le bug était réel — nextInt() a laissé le \n dans le tampon — et la correction orientée ligne (Integer.parseInt(s.nextLine().trim())) était la façon la plus propre d'éviter de mélanger les deux styles de lecture. Choisissez un style et tenez-vous-y.
  • Le bloc useDelimiter("," + "|" + "\\R") a analysé des lignes séparées par des virgules avec le même code de lecture de tokens, simplement avec un délimiteur différent. La même mise en garde s'applique comme dans le texte : cela fonctionne pour du CSV simple et échoue sur du CSV réel avec des champs entre guillemets. Utilisez une vraie bibliothèque CSV pour tout ce qui provient d'Excel.
  • Le pied de page d'entrée mixte (-- end --) a montré pourquoi hasNextInt() est la bonne condition de boucle : il a retourné false au premier token non entier et la boucle s'est terminée proprement. Passer à hasNext() aurait laissé la boucle continuer jusqu'à ce que nextInt() lève une exception — les deux formes sont utiles, selon que qu'un token non entier signifie « nous avons terminé » ou « l'entrée est incorrecte ».

Et ensuite

PrintWriter (le chapitre précédent) et Scanner sont les classes d'entrée/sortie orientées caractères que la plupart des codes Java d'introduction utilisent. Le prochain chapitre, Java PrintStream, couvre le pendant orienté octets de PrintWriter — et explique pourquoi System.out et System.err sont des PrintStream plutôt que des PrintWriter.

Pratique

Pratique
Sur une JVM avec l'allemand comme paramètre régional par défaut, vous appelez `scanner.nextDouble()` pour analyser '3.14' depuis un fichier de configuration. Que se passe-t-il, et quelle est la correction ?
Sur une JVM avec l'allemand comme paramètre régional par défaut, vous appelez `scanner.nextDouble()` pour analyser '3.14' depuis un fichier de configuration. Que se passe-t-il, et quelle est la correction ?
Was this page helpful?