Comment utiliser java.util.Scanner pour lire correctement l'entrée de l'utilisateur du système.et agir en conséquence?

il s'agit d'une question/réponse canonique qui peut être utilisé comme un double cible. Ces exigences sont fondées sur les questions publiées tous les jours et peuvent être ajoutés au besoin. Ils ont tous exiger la même structure de code de base pour accéder à chacun des scénarios et ils sont généralement dépendants les uns des autres.


le Scanner ressemble à un "simple" classe à utiliser, et c'est là la première erreur est faite. Il n'est pas simple, il a toutes sortes d'effets secondaires non-évidents et des comportements aberrants qui violent le principe de moindre étonnement de manière très subtile.

donc cela pourrait sembler exagéré pour cette classe, mais les erreurs et les problèmes de pelage des oignons sont tous simple , mais pris ensemble ils sont très complexe en raison de leurs interactions et effets secondaires. C'est pourquoi il y a tant de questions à ce sujet sur le débordement de la pile chaque jour.

Common Scanner questions:

Plus Scanner questions de tentatives ont échoué à plus d'une de ces choses.

  1. " je veux que mon programme attende automatiquement la prochaine entrée après chaque entrée précédente ainsi.

  2. je veux savoir comment détecter une commande exit et mettre fin à mon programme lorsque cette commande est entrée.

  3. je veux savoir comment faire correspondre plusieurs commandes pour la commande exit d'une manière non sensible à la casse.

  4. je veux être capable de faire correspondre les modèles d'expression réguliers ainsi que les primitives intégrées. Pour exemple, comment apparier ce qui semble être une date ( 2014/10/18 )?

  5. je veux savoir comment faire correspondre des choses qui pourraient ne pas être facilement implémentées avec une correspondance d'expression régulière - par exemple, une URL ( http://google.com ).

la Motivation:

dans le monde de Java, Scanner est un cas particulier, c'est une classe extrêmement délicate que les enseignants ne devraient pas donner aux nouveaux étudiants instructions pour l'utilisation. Dans la plupart des cas, les instructeurs ne sais même pas comment l'utiliser correctement. Il est à peine si jamais utilisé dans le code de production professionnelle, de sorte que sa valeur pour les étudiants est extrêmement discutable.

utiliser Scanner implique toutes les autres choses que cette question et réponse mentionne. Il ne s'agit pas seulement de Scanner il s'agit de résoudre ces problèmes communs avec Scanner qui sont toujours des problèmes comorbides dans presque toute la question qui obtiennent "151900920 de" mal. Il ne s'agit jamais seulement de next() vs nextLine() , qui est juste un symptôme de la finickiness de la mise en œuvre de la classe, il y a toujours d'autres problèmes dans le code affichant dans les questions demandant à propos de Scanner .

la réponse montre une mise en œuvre complète et idiomatique de 99% des cas où Scanner est utilisé et interrogé sur StackOverflow.

surtout en code débutant. Si vous pensez que cette réponse est trop complexe alors se plaindre aux instructeurs qui disent aux nouveaux étudiants d'utiliser Scanner avant d'expliquer les complexités, les bizarreries, les effets secondaires non-évidents et les particularités de son comportement.

Scanner est un grand moment d'enseignement sur l'importance du principe de moindre étonnement est et pourquoi un comportement cohérent et sémantique sont importants dans la dénomination des méthodes et des arguments de méthode.

Note aux étudiants:

vous ne verrez probablement jamais réellement Scanner utilisé dans professionnel / secteur commercial d'applications d'affaires parce que tout n'est mieux fait par quelque chose d'autre. Le logiciel du monde réel doit être plus résistant et maintenable que Scanner vous permet d'écrire code. Le logiciel Real world utilise des analyseurs de format de fichier standardisés et formats de fichier documentés, pas les adhoc formats d'entrée que vous être donné dans des affectations autonomes.

9
demandé sur 27 revsuser177800 2014-10-19 06:13:08

1 réponses

Exemple Idiomatique:

ce qui suit est la façon d'utiliser correctement la classe java.util.Scanner pour lire de manière interactive les entrées de l'utilisateur de System.in correctement( parfois appelé stdin , en particulier en C, C++ et d'autres langues ainsi que dans Unix et Linux). Il démontre idiomatiquement les choses les plus communes qui sont demandés pour être fait.

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:

Cela peut ressembler à beaucoup de code, mais il illustre le minimum effort nécessaire pour utiliser correctement la classe Scanner et ne pas avoir à faire face aux bugs subtils et les effets secondaires qui affligent ceux programmation et cette classe terriblement mise en œuvre appelé java.util.Scanner . Il tente d'illustrer ce code Java idiomatique devrait ressembler et se comporter comme des.

ci-dessous sont quelques-unes des choses que je pensais quand j'ai écrit cet exemple:

Version JDK:

j'ai volontairement gardé cet exemple compatible avec JDK 6. Si un scénario exige vraiment une fonctionnalité de JDK 7/8 I ou quelqu'un d'autre affichera une nouvelle réponse avec des détails sur la façon de modifier ceci pour cette version JDK.

la majorité des questions à propos de cette classe viennent des étudiants et ils ont généralement des restrictions sur ce qu'ils peuvent utiliser pour résoudre un problème donc j'ai restreint ceci autant que j'ai pu montrer comment faire les choses communes sans autres dépendances. dans les 22+ années que j'ai travaillé avec Java et la consultation de la majorité de ce temps, je n'ai jamais rencontré l'utilisation professionnelle de cette classe dans les 10 des millions de lignes de code source que j'ai vu.

commandes de traitement:

cela montre exactement comment idiomatiquement lire les commandes de l'utilisateur de façon interactive et d'envoyer ceux commande. La majorité des questions sur java.util.Scanner font partie de la catégorie Comment puis-je obtenir que mon programme cesse de fumer lorsque j'entre dans la catégorie . Cela montre clairement.

Régulateur Naïf

la logique d'expédition est intentionnellement naïve afin de ne pas compliquer la solution pour les nouveaux lecteurs. Un répartiteur, basé sur un Strategy Pattern ou Chain Of Responsibility serait plus approprié pour les problèmes du monde réel qui serait beaucoup plus complexes.

Traitement Des Erreurs

le code a été délibérément structuré de manière à ne pas exiger de manipulation Exception parce qu'il n'existe aucun scénario dans lequel certaines données pourraient ne pas être correctes.

.hasNext() et .hasNextXxx()

je vois rarement quelqu'un Utiliser le .hasNext() correctement, en testant pour le générique .hasNext() pour contrôler la boucle d'événement, et puis en utilisant le if(.hasNextXxx()) idiome vous permet décidez comment et quoi faire avec votre code sans avoir à vous soucier de demander un int quand aucun n'est disponible, donc pas de code de manipulation d'exception.

.nextXXX() vs .nextLine()

c'est quelque chose qui casse le code de tout le monde. C'est un détail délicat qui ne devrait pas avoir à être traité et a un bug très obstrué qui est difficile à raisonner à cause de cela casse le principe de Le Moins Étonnant

les méthodes .nextXXX() ne consomment pas la fin de ligne. .nextLine() ne.

cela signifie que l'appel .nextLine() immédiatement après .nextXXX() ne fera que retourner la ligne se terminant. Tu dois le rappeler pour avoir la ligne suivante.

C'est pourquoi beaucoup de gens préconisent soit de n'utiliser que les méthodes .nextXXX() ou seulement .nextLine() mais pas les deux à la fois pour que ce comportement délicat ne te fasse pas trébucher. Personnellement, je pense que les méthodes de type sûr sont beaucoup mieux que d'avoir ensuite tester et analyser et attraper les erreurs manuellement.

immutabilité:

notez qu'il n'y a pas de variables mutables utilisées dans le code, c'est important pour apprendre à le faire, il élimine quatre des sources les plus importantes d'erreurs d'exécution et de bugs subtils.

  1. Non nulls ça veut dire qu'il n'y a pas de NullPointerExceptions !

  2. aucune mutabilité signifie que vous n'avez pas à vous soucier des arguments de méthode changeant ou quoi que ce soit d'autre changeant. Lorsque vous procédez au débogage, vous n'avez jamais à utiliser watch pour voir quelles variables sont modifiées en quelles valeurs, si elles sont en train de changer. Cela rend la logique déterministe à 100% quand vous la lisez.

  3. aucune mutabilité signifie que votre code est automatiquement thread-safe.

  4. aucun effet secondaire. Si rien ne peut changer, vous n'avez pas à vous soucier d'un effet secondaire subtil d'un cas de bord changeant quelque chose inopinément!

lisez ceci si vous ne comprenez pas comment appliquer le mot-clé final dans votre propre code.

utilisant un ensemble au lieu de Massif switch ou if/elseif blocs:

remarquez comment j'utilise un Set<String> et utilise .contains() pour classer les commandes au lieu d'un énorme switch ou if/elseif monstruosité qui gonflerait votre code et plus important encore faire la maintenance un cauchemar! Ajouter une nouvelle commande surchargée est aussi simple que d'ajouter un nouveau String au tableau dans le constructeur.

cela aussi fonctionnerait très bien avec i18n et i10n et le proprement dit ResourceBundles . Un Map<Locale,Set<String>> vous permettrait d'avoir le soutien de plusieurs langues avec très peu de frais généraux!

@Non null

j'ai décidé que tout mon code devrait explicitement déclarer si quelque chose est @Nonnull ou @Nullable . Il permet à votre AIDE IDE de vous mettre en garde sur les dangers potentiels NullPointerException et quand vous n'avez pas à vérifier.

plus important encore, il documente l'attente pour les lecteurs futurs qu'aucun de ces paramètres de méthode ne devrait être null .

en l'Appelant .close ()

Vraiment réfléchir avant de le faire.

Que pensez-vous qu'il se passera System.in si vous deviez appeler sis.close() ? Voir les commentaires dans la liste ci-dessus.

s'il vous Plaît de la fourche et d'envoyer des pull requests et je mettrai à jour cette question et la réponse pour les autres base des scénarios d'utilisation.

10
répondu 27 revsuser177800 2018-09-10 19:40:01