Как использовать сканер java.util.Scanner для корректного считывания данных,вводимых пользователем из System.in,и как с ним работать

java java.util.scanner system.in


Это должен быть канонический вопрос / ответ, который можно использовать как дублирующую цель. Эти требования основаны на самых распространенных вопросах, публикуемых каждый день, и могут добавляться по мере необходимости. Все они требуют одинаковой базовой структуры кода, чтобы добраться до каждого из сценариев, и они, как правило, зависят друг от друга.


Сканер выглядит как «простой» класс для использования, и именно здесь совершается первая ошибка. Это не просто, у него есть все виды неочевидных побочных эффектов и аберрантного поведения, которые очень тонко нарушают принцип наименьшего удивления .

Так что это может показаться излишним для этого класса, но ошибки и проблемы при очистке лука все просты , но вместе взятые они очень сложны из-за их взаимодействия и побочных эффектов. Вот почему так много вопросов об этом каждый день.

Общие вопросы по сканеру:

Большинство вопросов Scanner включают неудачные попытки более чем одной из этих вещей.

  1. Я хочу,чтобы моя программа автоматически ждала следующего ввода после каждого предыдущего.

  2. Я хочу знать, как определить команду выхода и завершить программу, когда эта команда введена.

  3. Я хочу знать, как сопоставить несколько команд для команды выхода без учета регистра.

  4. Я хочу иметь возможность сопоставлять шаблоны регулярных выражений, а также встроенные примитивы. Например, как сопоставить то, что кажется датой ( 2014/10/18 )?

  5. Я хочу знать, как сопоставлять вещи, которые нелегко реализовать с сопоставлением по регулярным выражениям, например, URL ( http://google.com ).

Motivation:

В мире Java Scanner является особым случаем, это очень привередливый класс, и учителя не должны давать новым ученикам инструкции по использованию. В большинстве случаев инструкторы даже не знают, как правильно его использовать. Он вряд ли когда-либо используется в профессиональном производственном коде, поэтому его ценность для студентов крайне сомнительна.

Использование Scanner подразумевает все остальное, что упоминается в этом вопросе и ответе. Речь идет не только о Scanner а о том, как решить эти распространенные проблемы со Scanner , которые всегда являются сопутствующими проблемами почти во всех вопросах, которые ошибаются в Scanner . Это не просто next() против nextLine() , это всего лишь признак тонкости реализации класса, всегда есть другие проблемы при публикации кода в вопросах, касающихся Scanner .

Ответ показывает полную идиоматическую реализацию в 99% случаев, когда Scanner используется и задается вопрос о StackOverflow.

Особенно в коде для начинающих. Если вы считаете, что этот ответ слишком сложен, то пожаловаться учителям, которые советуют новым ученикам использовать Scanner прежде чем объяснять тонкости, причуды, неочевидные побочные эффекты и особенности его поведения.

Scanner - это прекрасный обучающий момент о том, как важен Принцип наименьшего удивления и почему согласованное поведение и семантика важны в именовании методов и аргументов методов.

Обратите внимание на студентов:

Вы, вероятно, никогда не увидите Scanner используемый в профессиональной / коммерческой линейке бизнес-приложений, потому что все, что он делает, лучше чем-то другим. Реальное программное обеспечение должно быть более устойчивым и обслуживаемым, чем Scanner позволяет писать код. Реальное программное обеспечение использует стандартизированные анализаторы форматов файлов и документированные форматы файлов, а не форматы ввода adhoc , которые вам даются в отдельных заданиях.




Answer 1 27 revsuser177800


Идиоматический пример:

Ниже показано, как правильно использовать класс java.util.Scanner для правильного интерактивного чтения пользовательского ввода из System.in (иногда его называют stdin , особенно в C, C ++ и других языках, а также в Unix и Linux). Это идиоматически демонстрирует наиболее распространенные вещи, которые просят сделать.

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:

Это может выглядеть как много кода, но оно иллюстрирует минимальные усилия, необходимые для правильного использования класса Scanner , и не нужно иметь дело с тонкими ошибками и побочными эффектами, которые мешают новичкам в программировании, и этому ужасно реализованному классу под названием java.util.Scanner , Он пытается проиллюстрировать, как должен выглядеть и вести себя идиоматический код Java.

Ниже приведены некоторые вещи, о которых я думал, когда писал этот пример:

Версия JDK:

Я специально сохранил этот пример совместимым с JDK 6.Если в каком-то сценарии действительно требуется функция JDK 7/8,я или кто-то другой напишет новый ответ с подробностями о том,как изменить это для этой версии JDK.

Большинство вопросов об этом классе приходят от студентов, и у них обычно есть ограничения на то, что они могут использовать для решения проблемы, поэтому я ограничил это настолько, насколько мог, чтобы показать, как делать обычные вещи без каких-либо других зависимостей. За 22 с лишним года, когда я работал с Java и консультировал большую часть времени, я никогда не сталкивался с профессиональным использованием этого класса в десятках миллионов строк исходного кода, который я видел.

Команды обработки:

Это показывает, как именно идиоматически читать команды пользователя в интерактивном режиме и отправлять эти команды. Большинство вопросов о java.util.Scanner касаются того, как я могу завершить свою программу, когда я вхожу в какую-то определенную категорию ввода . Это показывает это ясно.

наивный диспетчер

Логика рассылки намеренно наивна, чтобы не усложнять решение для новых читателей. Диспетчер, основанный на Strategy Pattern или Chain Of Responsibility , был бы более подходящим для задач реального мира, которые были бы гораздо более сложными.

обработка ошибок

Код был специально структурирован, чтобы не требовать обработки Exception потому что не существует сценария, в котором некоторые данные могут быть неверными.

.hasNext() и .hasNextXxx()

Я редко вижу, чтобы кто-то .hasNext() использовал .hasNext () , проверяя универсальный .hasNext() для управления циклом событий, а затем используя if(.hasNextXxx()) позволяющую вам решить, как и что делать с вашим кодом. без необходимости беспокоиться о запросе int , когда ни один не доступен, таким образом, нет кода обработки исключений.

.nextXXX() против .nextLine()

Это то, что нарушает код каждого. Это привередливая деталь, с которой не нужно разбираться, и в ней есть очень запутанная ошибка, о которой трудно рассуждать, потому что она нарушает Принцип Наименьшего Удивления

В .nextXXX() методы не потребляют конец строки. .nextLine() делает.

Это означает, что вызов .nextLine() сразу после .nextXXX() просто вернет окончание строки. Вы должны позвонить еще раз, чтобы получить следующую строку.

Вот почему многие люди рекомендуют либо использовать только методы .nextXXX() либо только .nextLine() но не оба одновременно, чтобы это не приводило вас в замешательство . Лично я считаю, что типобезопасные методы намного лучше, чем тестирование, анализ и обнаружение ошибок вручную.

Immutablity:

Обратите внимание,что в коде не используются мутируемые переменные,это важно для того,чтобы научиться делать,это устраняет четыре самых крупных источника ошибок во время выполнения и тонкие ошибки.

  1. Нет nulls означает отсутствие возможности исключения NullPointerExceptions !

  2. Отсутствие изменчивости означает, что вам не нужно беспокоиться об изменении аргументов метода или других изменениях. Когда вы проходите отладку, вам никогда не нужно использовать watch , чтобы увидеть, какие переменные изменяются на какие значения, если они меняются. Это делает логику 100% детерминированной, когда вы читаете ее.

  3. Отсутствие мутации означает,что ваш код автоматически является потокобезопасным.

  4. Никаких побочных эффектов.Если ничего не может измениться,вам не придется беспокоиться о каком-нибудь тонком побочном эффекте какого-нибудь крайнего случая,меняющего что-то неожиданно!

Прочтите это, если вы не понимаете, как применить final ключевое слово в вашем собственном коде.

Использование Set вместо массивного switch или блоков if/elseif :

Обратите внимание, как я использую Set<String> и использую .contains() для классификации команд вместо массивного switch или if/elseif чудовищность, которая раздувает ваш код и, что более важно, делает обслуживание кошмаром! Добавить новую перегруженную команду так же просто, как добавить новую String в массив в конструкторе.

Это также будет очень хорошо работать с i18n и i10n и соответствующими ResourceBundles . Map<Locale,Set<String>> позволит вам иметь поддержку нескольких языков с очень мало накладных расходов!

@Nonnull

Я решил, что весь мой код должен явно объявить, если что-то @Nonnull или @Nullable . Это позволяет вашей IDE предупредить вас о потенциальных опасностях NullPointerException и о том, когда вам не нужно проверять.

Наиболее важно, что он документирует ожидание будущих читателей, что ни один из этих параметров метода не должен быть null .

Вызов .close()

Правда,подумай об этом,прежде чем делать это.

Как вы думаете, что произойдет в System.in , если вы sis.close() ? Смотрите комментарии в листинге выше.

Пожалуйста, разветвите и отправьте запросы, и я обновлю этот вопрос и ответ для других основных сценариев использования.