teclado - ¿Cómo usar java.util.Scanner para leer correctamente la entrada del usuario de System.in y actuar en consecuencia?
scanner java int (1)
Está destinado a ser una pregunta / respuesta canónica que se puede utilizar como un objetivo duplicado. Estos requisitos se basan en las preguntas más comunes publicadas todos los días y se pueden agregar según sea necesario. Todos requieren la misma estructura de código básica para llegar a cada uno de los escenarios y generalmente dependen unos de otros.
Scanner parece una clase "simple" para usar, y ahí es donde se comete el primer error. No es simple, tiene todo tipo de efectos secundarios no obvios y comportamientos aberrantes que rompen el Principio de Menos Asombro de maneras muy sutiles.
Por lo tanto, esto puede parecer excesivo para esta clase, pero pelar los errores y los problemas de las cebollas son simples , pero en conjunto son muy complejos debido a sus interacciones y efectos secundarios. Es por eso que hay tantas preguntas al respecto en Stack Overflow todos los días.
Preguntas comunes del escáner:
La mayoría de las preguntas del
Scanner
incluyen intentos fallidos de más de una de estas cosas.
-
Quiero poder hacer que mi programa espere automáticamente la siguiente entrada después de cada entrada anterior también.
-
Quiero saber cómo detectar un comando de salida y finalizar mi programa cuando se ingresa ese comando.
-
Quiero saber cómo hacer coincidir múltiples comandos para el comando de salida sin distinción entre mayúsculas y minúsculas.
-
Quiero poder hacer coincidir los patrones de expresión regular, así como las primitivas incorporadas. Por ejemplo, ¿cómo hacer coincidir lo que parece ser una fecha (
2014/10/18
)? -
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 maestros no deben dar a los nuevos estudiantes instrucciones para usar.
En la mayoría de los casos, los instructores ni siquiera saben cómo usarlo correctamente.
Casi nunca se usa 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 menciona esta pregunta y respuesta.
Nunca se trata solo de
Scanner
, se trata de cómo resolver estos problemas comunes con
Scanner
que siempre son problemas comórbidos en casi todas las preguntas que hacen que
Scanner
equivoque.
Nunca se trata solo de
next()
vs
nextLine()
, eso es solo un síntoma de la delicadeza 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 pregunta sobre StackOverflow.
Especialmente en código de principiante.
Si cree que esta respuesta es demasiado compleja, entonces quejarse con los instructores que les dicen a los nuevos estudiantes que usen
Scanner
antes de explicar las complejidades, peculiaridades, efectos secundarios no obvios y peculiaridades de su comportamiento.
Scanner
es un gran momento de enseñanza acerca de cuán importante es el
Principio de menor asombro
y por qué el comportamiento consistente y la semántica son importantes para nombrar métodos y argumentos de métodos.
Nota para los alumnos:
Probablemente nunca verá que
Scanner
use en aplicaciones de línea de negocios profesionales / comerciales porque todo lo que hace lo hace mejor otra cosa. El software del mundo real tiene que ser más resistente y fácil de mantener de lo queScanner
permite escribir código. El software del mundo real utiliza analizadores de formatos de archivo estandarizados y formatos de archivo documentados, no los formatos de entrada ad hoc que se le asignan en asignaciones independientes.
Ejemplo idiomático:
Lo siguiente es cómo usar correctamente la clase
java.util.Scanner
para leer interactivamente la entrada del usuario de
System.in
correctamente (a veces denominada
stdin
, especialmente en C, C ++ y otros lenguajes, así como en Unix y Linux).
Demuestra idiomáticamente las cosas más comunes que se deben hacer.
package com..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<String> EXIT_COMMANDS;
private static final Set<String> HELP_COMMANDS;
private static final Pattern DATE_PATTERN;
private static final String HELP_MESSAGE;
static
{
final SortedSet<String> ecmds = new TreeSet<String>(String.CASE_INSENSITIVE_ORDER);
ecmds.addAll(Arrays.asList("exit", "done", "quit", "end", "fino"));
EXIT_COMMANDS = Collections.unmodifiableSortedSet(ecmds);
final SortedSet<String> hcmds = new TreeSet<String>(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
Scanner
correctamente y no tener que lidiar con errores sutiles y efectos secundarios que afectan a los nuevos en la programación y esta clase terriblemente implementada llamadajava.util.Scanner
. Intenta ilustrar cómo debería ser y comportarse el código idiomático de Java.
A continuación se presentan 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 u otra persona publicaremos una nueva respuesta con detalles sobre cómo modificar esto para esa versión 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ía de las veces, nunca he visto el uso profesional de esta clase en el código fuente de 10 de millones de líneas que he visto.
Procesando comandos:
Esto muestra exactamente cómo leer los comandos del usuario de forma interactiva y distribuir esos comandos.
La mayoría de las preguntas sobre
java.util.Scanner
son sobre
cómo puedo hacer que mi programa se cierre cuando ingreso alguna
categoría de
entrada específica
.
Esto lo demuestra claramente.
Despachador ingenuo
La lógica de despacho es intencionadamente ingenua para no complicar la solución a los nuevos lectores.
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 estructurado deliberadamente para no requerir manejo de
Exception
porque no existe un escenario en el que algunos datos podrían no ser correctos.
.hasNext()
y
.hasNextXxx()
Raramente veo a alguien que use el
.hasNext()
correctamente, al probar el genérico
.hasNext()
para controlar el bucle de eventos, y luego usar el idioma
if(.hasNextXxx())
permite decidir cómo y qué proceder con su código sin tener que preocuparse por pedir un
int
cuando no hay ninguno disponible, por lo tanto, no hay código de manejo de excepciones.
.nextXXX()
vs
.nextLine()
Esto es algo que rompe el código de todos. Es un detalle meticuloso que no debería tratarse y tiene un error muy ofuscado que es difícil de razonar debido a que rompe el principio de menor asombro
Los métodos
.nextXXX()
no consumen el final de línea.
.nextLine()
hace.
Eso significa que llamar a
.nextLine()
inmediatamente después de
.nextXXX()
solo devolverá el final de la línea.
Debe llamarlo nuevamente para obtener la siguiente línea.
Esta es la razón por la cual muchas personas abogan por usar solo los métodos
.nextXXX()
o solo
.nextLine()
pero no ambos al mismo tiempo para que este comportamiento delicado no te haga tropezar.
Personalmente, creo que los métodos de tipo seguro son mucho mejores que tener que probar, analizar y detectar errores manualmente.
Inmutabilidad:
Tenga en cuenta que no hay variables mutables utilizadas en el código, esto es importante para aprender cómo hacerlo, ya que elimina cuatro de las principales fuentes de errores de tiempo de ejecución y errores sutiles.
-
¡Sin
nulls
significa que no hay posibilidad deNullPointerExceptions
! -
Sin mutabilidad significa que no tiene que preocuparse por el cambio de los argumentos del método o cualquier otra cosa. Cuando realiza la depuración, nunca tiene que usar
watch
para ver qué variables cambian a qué valores, si cambian. Esto hace que la lógica sea 100% determinista cuando la lees. -
Sin mutabilidad significa que su código es automáticamente seguro para subprocesos.
-
Sin efectos secundarios. Si nada puede cambiar, ¡no tiene que preocuparse por algún efecto secundario sutil de algún caso límite que cambie algo inesperadamente!
Lea esto si no comprende cómo aplicar la palabra clave
final
en su propio código.
Usando un Set en lugar de un
switch
masivo o bloques
if/elseif
:
¡Observe cómo uso un
Set<String>
y uso
.contains()
para clasificar los comandos en lugar de un
switch
masivo o
if/elseif
monstruosidad hinchara su código y lo más importante, haría que el mantenimiento sea una pesadilla!
Agregar un nuevo comando sobrecargado es tan simple como agregar una nueva
String
a la matriz en el constructor.
Esto también funcionaría muy bien con
i18n
e
i10n
y los
ResourceBundles
adecuados.
¡Un
Map<Locale,Set<String>>
le permitiría tener soporte para múltiples idiomas con muy poca sobrecarga!
@Nonnull
He decidido que todo mi código debería declarar
explicitly
si algo es
@Nonnull
o
@Nullable
.
Permite que su IDE le advierta sobre posibles peligros de
NullPointerException
y cuando no tiene que verificar.
Lo más importante es que documenta la expectativa para futuros lectores de que ninguno de estos parámetros del método debe ser
null
.
Llamando a .close ()
Realmente piense en esto antes de hacerlo.
¿Qué crees que pasará
System.in
si llamaras a
sis.close()
?
Vea los comentarios en la lista anterior.