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

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


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

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

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

  • Я хочу знать, как обнаружить команду exit и завершить мою программу, когда эта команда будет введена.

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

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

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

Мотивация:

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

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

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

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

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

Примечание для учащихся:

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

Ответы

Ответ 1

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

Ниже приведен пример правильного использования класса java.util.Scanner для интерактивного чтения пользовательского ввода из System.in правильно (иногда называемого stdin, особенно на языках 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);
    }
}

Примечания:

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

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

Версия JDK:

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

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

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

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

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

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

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

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

.hasNext() и .hasNextXxx()

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

.nextXXX() vs .nextLine()

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

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

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

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

Immutablity:

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

  • Нет nulls означает отсутствие возможности NullPointerExceptions!

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

  • Никакая изменчивость означает, что ваш код автоматически потокобезопасен.

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

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

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

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

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

@Nonnull

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

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

Вызов .close()

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

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

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