Cómo usar el escáner java.util para leer correctamente los datos del usuario del sistema y actuar en consecuencia.

java java.util.scanner system.in


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.

  1. Quiero que mi programa 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 programa cuando se ingresa ese comando.

  3. Quiero saber cómo hacer coincidir múltiples comandos para el comando de salida sin distinción entre mayúsculas y minúsculas.

  4. 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 )?

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

Motivation:

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 se 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 menos 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 estudiantes:

Probablemente nunca verá que Scanner se 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 que Scanner le permite escribir código. El software del mundo real utiliza analizadores de formato de archivo estandarizados y formatos de archivo documentados, no los formatos de entrada ad hoc que se le asignan en asignaciones independientes.




Answer 1 27 revsuser177800


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.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<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);
    }
}

Notes:

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 llamada java.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 requiere una característica de JDK 7/8 I o alguien más publicará 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, por lo 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 demuestra exactamente cómo idiomáticamente leen los comandos de forma interactiva al usuario y envían 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.

Despachante 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 patrón de 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 porque 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.

Immutablity:

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

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

  2. 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.

  3. Si no hay mutabilidad,el código es automáticamente seguro.

  4. No hay efectos secundarios.Si nada puede cambiar,no tienes que preocuparte por algún sutil efecto secundario 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 de lo contrario, una monstruosidad que inflaría su código y, lo que es más importante, hará 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 explícitamente 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 .close()

Piensa de verdad en esto antes de hacerlo.

¿Qué crees que pasará System.in si llamaras a sis.close() ? Vea los comentarios en la lista anterior.

Por favor solicitudes tenedor y enviar tracción y voy a actualizar esta pregunta y respuesta para otros escenarios de uso básico.