W3docs

Clase Scanner de Java

Analiza primitivos y cadenas desde entrada de texto en Java con la clase Scanner — nextInt, nextLine, useDelimiter.

BufferedReader.readLine() del capítulo de flujos con buffer es la herramienta adecuada cuando la entrada está orientada a líneas y quieres cada línea como un String. Scanner es la herramienta adecuada cuando la entrada es un flujo de tokens — enteros, dobles, palabras separadas por espacios en blanco, o campos separados por una expresión regular que tú elijas. Es el analizador que el JDK incluye de forma nativa.

Scanner también es la clase que la mayoría de los tutoriales introductorios de Java usan para leer desde el teclado. new Scanner(System.in) y tienes un programa interactivo funcional en dos líneas. Esa conveniencia viene con un problema conocido — la trampa de nextInt/nextLine — de la cual trata principalmente este capítulo.

Qué analiza Scanner

Los métodos de lectura de tokens, emparejados con sus predicados 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

El contrato es idéntico en todos los métodos tipados: hasNextX() comprueba si el siguiente token se puede analizar como X sin consumirlo; nextX() lo consume. Una discordancia (nextInt() cuando el token es "hello") lanza InputMismatchException. El fin del flujo lanza NoSuchElementException.

Un token es, por defecto, una secuencia máxima de caracteres que no son espacios en blanco. El patrón delimitador es lo que Pattern.UNICODE_CHARACTER_CLASS considera espacio en blanco — espacios, tabulaciones, saltos de línea y similares. Puedes cambiarlo con useDelimiter(...).

Constructores

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, ...)

La misma regla que en el resto de java.io/java.nio: siempre pasa un charset explícito al leer bytes. Los constructores sin charset usan la codificación de la plataforma por defecto.

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

Cerrar el Scanner cierra el flujo subyacente. No cierres un Scanner que envuelva System.in — cerrarlo cierra System.in, y cualquier lectura posterior en la misma JVM fallará.

La trampa de nextInt / nextLine

La pregunta sobre Java más frecuente en Stack Overflow.

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

Escribe 30, pulsa Enter, luego Alice, pulsa Enter. Esperado: age=30, name=Alice. Real: age=30, name="".

La razón: nextInt() lee los dígitos 30 y se detiene. Deja el \n final en el búfer de entrada. El siguiente nextLine() lee todo hasta el siguiente salto de línea — que está justo ahí, de inmediato — y devuelve la cadena vacía antes de que el usuario haya tenido oportunidad de escribir nada.

La solución es una de estas:

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

o, de forma más robusta, analizar la línea completa tú mismo:

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

El segundo patrón es el que uso en código real. Mezclar métodos de lectura de tokens (nextInt, nextDouble, next) con lectura de líneas (nextLine) es una receta para errores por desplazamiento de uno; elige uno y quédate con él. O analiza línea por línea con nextLine, o analiza token por token con next* y usa nextLine solo para el propósito explícito de "saltar el resto de esta línea".

hasNext como condición del bucle

La forma de todo bucle con Scanner:

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

hasNextInt() devuelve false al final del flujo y cuando el siguiente token no es un entero — así el bucle termina limpiamente en EOF y en un token no numérico (que suele ser lo correcto, por ejemplo cuando el pie de página final no es numérico). Si quieres fallar de forma ruidosa en su lugar, usa hasNext() y deja que nextInt() lance InputMismatchException en caso de discordancia:

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

La misma comprobación de fin de flujo, comportamiento diferente con tokens incorrectos.

Delimitadores personalizados

El delimitador por defecto es el espacio en blanco. Para entradas tipo CSV puedes cambiarlo:

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

\\R es la expresión regular de Java para "cualquier secuencia de salto de línea" (\n, \r\n, \r, más los separadores de línea Unicode). El patrón combinado divide en comas y saltos de línea, por lo que 1,2,3\n4,5,6 produce seis tokens.

Dicho esto: para CSV real, usa una biblioteca CSV. Scanner no maneja campos entre comillas, comas escapadas o saltos de línea incrustados. Para los casos simples — una lista de números, una configuración separada por espacios — es perfecto.

El problema de la configuración regional

nextDouble() analiza con el separador decimal de la configuración regional por defecto. En una JVM alemana, 3.14 falla (3,14 es la forma alemana). En una JVM de EE. UU., 3,14 falla.

Para entradas legibles por máquina, fuerza la configuración regional del analizador:

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

Locale.ROOT es la configuración regional "neutral" — la convención para analizar archivos de datos que no están destinados a humanos. Olvidar esto es la razón más común por la que un lector CSV funciona en desarrollo y falla en CI: el equipo de desarrollo y el servidor de CI tienen configuraciones regionales por defecto diferentes.

Scanner vs BufferedReader

ScannerBufferedReader
Leetokens (tipados)líneas (String)
Velocidadlento (regex en cada token)rápido
Comodidadalta (nextInt etc.)baja (lo analizas tú mismo)
Adecuado paraentradas pequeñas, prompts interactivos, pruebasarchivos grandes, procesamiento de logs, bucles intensivos

Regla general: si la entrada es de un humano y quieres tipos, usa Scanner. Si la entrada es un archivo y quieres líneas, usa BufferedReader. Para entradas de tamaño propio de programación competitiva (millones de tokens), BufferedReader + StringTokenizer es un orden de magnitud más rápido que Scanner.

Un ejemplo práctico: análisis de un formato de texto pequeño

El programa a continuación analiza un pequeño archivo de texto delimitado por espacios con tres registros por línea — id name score — usando Scanner. Demuestra el bucle con hasNextInt(), la corrección de configuración regional para nextDouble(), la trampa de nextInt/nextLine y su resolución, y finalmente useDelimiter para una alternativa tipo CSV.

java— editable, runs on the server

Qué extraer de la ejecución:

  • La primera lectura analizó tres registros de tres tipos distintos en tres líneas de código. La API basada en tokens es genuinamente conveniente cuando la entrada tiene forma de tokens — sin regex, sin String.split, sin Integer.parseInt manual. Ese es el caso de Scanner.
  • useLocale(Locale.ROOT) fue la línea que hizo que 97.5 fuera analizable. Sin ella, el analizador usa la configuración regional por defecto de la JVM; en una máquina donde esa es la alemana, 97.5 lanzaría InputMismatchException. Para entradas legibles por máquina, siempre fija la configuración regional.
  • La división con error/corrección para la trampa imprimió name='' y luego name='Alice'. El error era real — nextInt() dejó el \n en el búfer — y la corrección orientada a líneas (Integer.parseInt(s.nextLine().trim())) fue la forma más limpia de evitar mezclar los dos estilos de lectura. Elige un estilo y quédate con él.
  • El bloque useDelimiter("," + "|" + "\\R") analizó filas separadas por comas con el mismo código de lectura de tokens, solo con un delimitador diferente. La misma advertencia aplica como en el texto: esto funciona para CSV limpio y falla con CSV del mundo real con campos entre comillas. Usa una biblioteca CSV real para cualquier cosa que venga de Excel.
  • El pie de página de entrada mixta (-- end --) mostró por qué hasNextInt() es la condición de bucle correcta: devolvió false en el primer token no entero y el bucle terminó limpiamente. Cambiar a hasNext() habría dejado que el bucle continuara hasta que nextInt() lanzara — ambas formas son útiles, dependiendo de si un token no entero significa "hemos terminado" o "la entrada está rota".

Qué sigue

PrintWriter (el capítulo anterior) y Scanner son las clases de entrada/salida orientadas a caracteres que usa la mayoría del código introductorio de Java. El siguiente capítulo, Java PrintStream, cubre el hermano orientado a bytes de PrintWriter — y explica por qué System.out y System.err son PrintStreams en lugar de PrintWriters.

Práctica

Práctica
En una JVM con alemán como configuración regional por defecto, llamas a `scanner.nextDouble()` para analizar '3.14' desde un archivo de configuración. ¿Qué ocurre y cuál es la solución?
En una JVM con alemán como configuración regional por defecto, llamas a `scanner.nextDouble()` para analizar '3.14' desde un archivo de configuración. ¿Qué ocurre y cuál es la solución?
Was this page helpful?