¿Cómo usar java.util.Scanner para leer correctamente la entrada de usuario de System.in y actuar sobre ella?

Esta debe ser una pregunta / respuesta canónica que se puede usar como un objective duplicado. Estos requisitos se basan en las preguntas más comunes publicadas todos los días y pueden agregarse según sea necesario. Todos ellos requieren la misma estructura básica de código para llegar a cada uno de los escenarios y generalmente dependen el uno del otro.


Preguntas comunes sobre el escáner:

La mayoría de las preguntas del Scanner incluyen bashs fallidos en más de una de estas cosas.

  1. Quiero poder hacer que mi progtwig espere automáticamente la siguiente entrada después de cada entrada anterior también.

  2. Quiero saber cómo detectar un comando de salida y finalizar mi progtwig cuando se ingresa ese comando.

  3. Quiero saber cómo unir múltiples comandos para el comando de salida de una manera que no distinga entre mayúsculas y minúsculas.

  4. Quiero ser capaz de coincidir con los patrones de expresión regulares, así como las primitivas incorporadas. Por ejemplo, ¿cómo hacer coincidir lo que parece ser una fecha ( 2014/10/18 )?

  5. Quiero saber cómo hacer coincidir cosas que podrían no implementarse fácilmente con la coincidencia de expresiones regulares, por ejemplo, una URL ( http://google.com ).

Motivación:

En el mundo de Java, Scanner es un caso especial, es una clase extremadamente delicada que los profesores no deben dar instrucciones a los nuevos estudiantes para usar. En la mayoría de los casos, los instructores ni siquiera saben cómo usarlo correctamente. Es apenas utilizado en el código de producción profesional, por lo que su valor para los estudiantes es extremadamente cuestionable.

Usar Scanner implica todas las otras cosas que mencionan esta pregunta y respuesta. Nunca se trata solo de Scanner , se trata de cómo resolver estos problemas comunes con Scanner que son siempre problemas co mórbidos en casi todas las preguntas que hacen que Scanner equivoque. Nunca se trata de next() versus nextLine() , que es solo un síntoma de la complejidad de la implementación de la clase, siempre hay otros problemas en la publicación del código en las preguntas sobre Scanner .

La respuesta muestra una implementación completa e idiomática del 99% de los casos en los que se usa Scanner y se le preguntó acerca de StackOverflow.

Especialmente en el código para principiantes. Si crees que esta respuesta es demasiado compleja, pregúntale a los instructores que le dicen a los nuevos estudiantes que usen Scanner antes de explicar las complejidades, peculiaridades y peculiaridades de su comportamiento.

Scanner es el gran momento de enseñanza sobre cuán importante es el Principio de menor asombro y por qué el comportamiento y la semántica consistentes son importantes para nombrar métodos y argumentos de método.

Nota para los estudiantes:

Probablemente nunca verás Scanner utilizado en la línea de negocios profesional / comercial porque todo lo que hace se hace mejor por otra cosa. El software del mundo real tiene que ser más flexible y fácil de mantener que Scanner permite escribir código. El software del mundo real utiliza analizadores de formatos de archivos estandarizados y formatos de archivo documentados, no los formatos de entrada adhoc que se le dan en las asignaciones independientes.

Ejemplo idiomático:

A continuación se muestra cómo utilizar correctamente la clase java.util.Scanner para leer de forma interactiva la entrada de usuario de System.in correctamente (a veces denominado stdin , especialmente en C, C ++ y otros lenguajes, así como en Unix y Linux). Muestra idiomáticamente las cosas más comunes que se solicitan.

 package com.stackoverflow.scanner; import javax.annotation.Nonnull; import java.math.BigInteger; import java.net.MalformedURLException; import java.net.URL; import java.util.*; import java.util.regex.Pattern; import static java.lang.String.format; public class ScannerExample { private static final Set EXIT_COMMANDS; private static final Set HELP_COMMANDS; private static final Pattern DATE_PATTERN; private static final String HELP_MESSAGE; static { final SortedSet ecmds = new TreeSet(String.CASE_INSENSITIVE_ORDER); ecmds.addAll(Arrays.asList("exit", "done", "quit", "end", "fino")); EXIT_COMMANDS = Collections.unmodifiableSortedSet(ecmds); final SortedSet hcmds = new TreeSet(String.CASE_INSENSITIVE_ORDER); hcmds.addAll(Arrays.asList("help", "helpi", "?")); HELP_COMMANDS = Collections.unmodifiableSet(hcmds); DATE_PATTERN = Pattern.compile("\\d{4}([-\\/])\\d{2}\\1\\d{2}"); // http://regex101.com/r/xB8dR3/1 HELP_MESSAGE = format("Please enter some data or enter one of the following commands to exit %s", EXIT_COMMANDS); } /** * Using exceptions to control execution flow is always bad. * That is why this is encapsulated in a method, this is done this * way specifically so as not to introduce any external libraries * so that this is a completely self contained example. * @param s possible url * @return true if s represents a valid url, false otherwise */ private static boolean isValidURL(@Nonnull final String s) { try { new URL(s); return true; } catch (final MalformedURLException e) { return false; } } private static void output(@Nonnull final String format, @Nonnull final Object... args) { System.out.println(format(format, args)); } public static void main(final String[] args) { final Scanner sis = new Scanner(System.in); output(HELP_MESSAGE); while (sis.hasNext()) { if (sis.hasNextInt()) { final int next = sis.nextInt(); output("You entered an Integer = %d", next); } else if (sis.hasNextLong()) { final long next = sis.nextLong(); output("You entered a Long = %d", next); } else if (sis.hasNextDouble()) { final double next = sis.nextDouble(); output("You entered a Double = %f", next); } else if (sis.hasNext("\\d+")) { final BigInteger next = sis.nextBigInteger(); output("You entered a BigInteger = %s", next); } else if (sis.hasNextBoolean()) { final boolean next = sis.nextBoolean(); output("You entered a Boolean representation = %s", next); } else if (sis.hasNext(DATE_PATTERN)) { final String next = sis.next(DATE_PATTERN); output("You entered a Date representation = %s", next); } else // unclassified { final String next = sis.next(); if (isValidURL(next)) { output("You entered a valid URL = %s", next); } else { if (EXIT_COMMANDS.contains(next)) { output("Exit command %s issued, exiting!", next); break; } else if (HELP_COMMANDS.contains(next)) { output(HELP_MESSAGE); } else { output("You entered an unclassified String = %s", next); } } } } /* This will close the underlying InputStream, in this case System.in, and free those resources. WARNING: You will not be able to read from System.in anymore after you call .close(). If you wanted to use System.in for something else, then don't close the Scanner. */ sis.close(); System.exit(0); } } 

Notas:

Esto puede parecer mucho código, pero ilustra el esfuerzo mínimo necesario para usar la clase de Scanner correctamente y no tener que lidiar con errores sutiles y efectos secundarios que plagan a los nuevos en la progtwigción y esta clase terriblemente implementada llamada java.util.Scanner . Trata de ilustrar cómo debería ser el código idiomático de Java y comportarse así.

A continuación se encuentran algunas de las cosas en las que estaba pensando cuando escribí este ejemplo:

Versión JDK:

A propósito mantuve este ejemplo compatible con JDK 6. Si algún escenario realmente exige una característica de JDK 7/8, yo o alguien más publicaré una nueva respuesta con detalles sobre cómo modificar esto para esa versión de JDK.

La mayoría de las preguntas sobre esta clase provienen de estudiantes y generalmente tienen restricciones sobre lo que pueden usar para resolver un problema, así que restringí esto tanto como pude para mostrar cómo hacer las cosas comunes sin ninguna otra dependencia. En los más de 22 años que he estado trabajando con Java y consultando la mayor parte de ese tiempo, nunca he encontrado uso profesional de esta clase en el código fuente de 10 millones de líneas que he visto.

Procesando comandos:

Esto muestra exactamente cómo leer idiomáticamente los comandos del usuario de forma interactiva y enviar esos comandos. La mayoría de las preguntas sobre java.util.Scanner son de cómo puedo hacer que mi progtwig se cierre cuando ingreso una categoría de entrada específica . Esto lo muestra claramente.

Naive Dispatcher

La lógica de envío es intencionalmente ingenua para no complicar la solución a los lectores nuevos. Un despachador basado en un Strategy Pattern o un Strategy Pattern Chain Of Responsibility sería más apropiado para problemas del mundo real que serían mucho más complejos.

Manejo de errores

El código fue deliberadamente estructurado para no requerir el manejo de Exception porque no existe un escenario donde algunos datos podrían no ser correctos.

.hasNext() y .hasNextXxx()

Rara vez veo a alguien usando el .hasNext() correctamente, probando el genérico .hasNext() para controlar el bucle de evento, y luego usando el if(.hasNextXxx()) permite decidir cómo y qué hacer con su código sin tener que preocuparse por pedir un int cuando ninguno está disponible, por lo tanto, no hay un código de manejo de excepciones.

.nextXXX() versus .nextLine()

Esto es algo que rompe el código de todos. Es un detalle meticuloso que no debería tener que tratarse y tiene un error muy obvio que es difícil de razonar porque rompe el principio de asombro mínimo.

Los métodos .nextXXX() no consumen el final de línea. .nextLine() hace.

Eso significa que al llamar a .nextLine() inmediatamente después de .nextXXX() simplemente se devolverá el final de línea. Tienes que volver a llamar para obtener la siguiente línea.

Tantas personas abogan por usar solo los métodos .nextXXX() o solo .nextLine() pero no ambos para que este comportamiento meticuloso no lo haga tropezar.

Immutablity:

Tenga en cuenta que no hay variables mutables utilizadas en el código, esto es importante para aprender a hacer, elimina cuatro de las fonts más importantes de errores de tiempo de ejecución y errores sutiles.

  1. ¡Sin nulls significa que no hay posibilidad de NullPointerExceptions !

  2. Ninguna mutabilidad significa que no tiene que preocuparse de que los argumentos del método cambien o cambien cualquier otra cosa. Cuando realiza la depuración, nunca tiene que utilizar el watch para ver qué variables cambian a qué valores, si están cambiando. Esto hace que la lógica sea 100% determinista cuando la lees.

  3. Ninguna mutabilidad significa que su código es automáticamente seguro para subprocesos.

  4. Sin efectos secundarios. Si nada puede cambiar, no tiene que preocuparse por algún efecto secundario sutil de alguna funda lateral que cambie algo inesperadamente.

Lea esto si no entiende cómo aplicar la palabra clave final en su propio código.

Usando un conjunto en lugar de un switch masivo o bloques if/elseif :

Observe cómo utilizo un Set y uso .contains() para clasificar los comandos en lugar de un switch masivo o if/elseif monstruosidad que hincharía su código y lo que es más importante, ¡haría el mantenimiento una pesadilla! Agregar un nuevo comando sobrecargado es tan simple como agregar un nuevo String a la matriz en el constructor.

Esto también funcionaría muy bien con i18n e i10n y con los ResourceBundles adecuados. Un Map> le permitiría tener soporte para múltiples idiomas con muy poca sobrecarga.

@Nonnull

He decidido que todo mi código debería declarar explícitamente si algo es @Nonnull o @Nullable . Le permite a su IDE ayudarlo a advertirle sobre los riesgos potenciales de NullPointerException y cuando no tiene que verificarlo.

Lo más importante es que documenta la expectativa para los lectores futuros de que ninguno de estos parámetros de método debería ser null .

Llamar a .close ()

Piensa realmente en esto antes de hacerlo.

¿Qué crees que sucederá System.in si llamaras a sis.close() ? Ver los comentarios en el listado superior.

Hornee y envíe solicitudes de extracción y actualizaré esta pregunta y respuesta para otros escenarios de uso básico.