简体   繁体   English

如何使用 java.util.Scanner 从 System.in 正确读取用户输入并对其采取行动?

[英]How to use java.util.Scanner to correctly read user input from System.in and act on it?

This is meant to be a canonical question/answer that can be used as a duplicate target.这是一个可以用作重复目标的规范问题/答案 These requirements are based on the most common questions posted every day and may be added to as needed.这些要求基于每天发布的最常见问题,并可根据需要添加。 They all require the same basic code structure to get to each of the scenarios and they are generally dependent on one another.它们都需要相同的基本代码结构才能到达每个场景,并且它们通常相互依赖。


Scanner seems like a "simple" class to use, and that is where the first mistake is made. Scanner 似乎是一个使用起来“简单”的类,这就是犯第一个错误的地方。 It is not simple, it has all kinds of non-obvious side effect and aberrant behaviors that break the Principle of Least Astonishment in very subtle ways.它并不简单,它具有各种不明显的副作用和异常行为,以非常微妙的方式打破了最小惊讶原则

So this might seem to be overkill for this class, but the peeling the onions errors and problems are all simple , but taken together they are very complex because of their interactions and side effects.所以这对这个类来说似乎有点矫枉过正,但是剥洋葱的错误和问题都很简单,但是由于它们的相互作用和副作用,它们加在一起非常复杂 This is why there are so many questions about it on Stack Overflow every day.这就是为什么每天在 Stack Overflow 上都有很多关于它的问题。

Common Scanner questions:扫描仪常见问题:

Most Scanner questions include failed attempts at more than one of these things.大多数Scanner问题都包括尝试不止一项的失败。

  1. I want to be able to have my program automatically wait for the next input after each previous input as well.我希望能够让我的程序在每次之前的输入之后自动等待下一个输入。

  2. I want to know how to detect an exit command and end my program when that command is entered.我想知道如何检测退出命令并在输入该命令时结束我的程序。

  3. I want to know how to match multiple commands for the exit command in a case-insensitive way.我想知道如何以不区分大小写的方式为exit命令匹配多个命令。

  4. I want to be able to match regular expression patterns as well as the built-in primitives.我希望能够匹配正则表达式模式以及内置原语。 For example, how to match what appears to be a date ( 2014/10/18 )?例如,如何匹配似乎是日期的内容( 2014/10/18 )?

  5. I want to know how to match things that might not easily be implemented with regular expression matching - for example, an URL ( http://google.com ).我想知道如何匹配可能不容易用正则表达式匹配实现的东西 - 例如,一个 URL ( http://google.com )。

Motivation:动机:

In the Java world, Scanner is a special case, it is an extremely finicky class that teachers should not give new students instructions to use.在 Java 世界中, Scanner是一个特例,它是一个非常挑剔的类,老师不应该给新学生使用说明。 In most cases the instructors do not even know how to use it correctly.在大多数情况下,教师甚至不知道如何正确使用它。 It is hardly if ever used in professional production code so its value to students is extremely questionable.它几乎没有用于专业生产代码,因此它对学生的价值非常值得怀疑。

Using Scanner implies all the other things this question and answer mentions.使用Scanner意味着这个问题和答案提到的所有其他事情。 It is never just about Scanner it is about how to solve these common problems with Scanner that are always co morbid problems in almost all the question that get Scanner wrong.它绝不仅仅是关于Scanner ,而是关于如何解决Scanner这些常见问题,这些问题在几乎所有使Scanner出错的问题中都是共病问题。 It is never just about next() vs nextLine() , that is just a symptom of the finickiness of the implementation of the class, there are always other issues in the code posting in questions asking about Scanner .它永远不仅仅是关于next()nextLine() ,这只是类实现的挑剔的一个症状,在询问Scanner问题中发布的代码中总是存在其他问题。

The answer shows a complete, idiomatic implementation of 99% of cases where Scanner is used and asked about on StackOverflow.答案显示了 99% 使用Scanner并在 StackOverflow 上被询问的情况的完整、惯用的实现。

Especially in beginner code.特别是在初学者代码中。 If you think this answer is too complex then complain to the instructors that tell new students to use Scanner before explaining the intricacies, quirks, non-obvious side effects and peculiarities of its behavior.如果你认为这个答案太复杂,那么在解释其行为的复杂性、怪癖、不明显的副作用和特殊性之前,向告诉新学生使用Scanner的教师抱怨。

Scanner is the a great teaching moment about how important the Principle of least astonishment is and why consistent behavior and semantics are important in naming methods and method arguments. Scanner是关于 最小惊讶原则的重要性以及为什么一致的行为和语义在命名方法和方法参数中很重要的一个很好的教学时刻。

Note to students:学生须知:

You will probably never actually see Scanner used in professional/commercial line of business apps because everything it does is done better by something else.您可能永远不会真正看到Scanner用于专业/商业业务线应用程序,因为它所做的一切都被其他东西做得更好。 Real world software has to be more resilient and maintainable than Scanner allows you to write code.现实世界的软件必须比Scanner允许您编写代码更具弹性和可维护性。 Real world software uses standardized file format parsers and documented file formats, not the adhoc input formats that you are given in stand alone assignments.现实世界的软件使用标准化的文件格式解析器和文档化的文件格式,而不是您在独立作业中给出的即席输入格式。

Idiomatic Example:惯用语示例:

The following is how to properly use the java.util.Scanner class to interactively read user input from System.in correctly( sometimes referred to as stdin , especially in C, C++ and other languages as well as in Unix and Linux).以下是如何正确使用java.util.Scanner类以交互方式正确读取System.in用户输入(有时称为stdin ,尤其是在 C、C++ 和其他语言以及在 Unix 和 Linux 中)。 It idiomatically demonstrates the most common things that are requested to be done.它惯用地演示了要求完成的最常见的事情。

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:笔记:

This may look like a lot of code, but it illustrates the minimum effort needed to use the Scanner class correctly and not have to deal with subtle bugs and side effects that plague those new to programming and this terribly implemented class called java.util.Scanner .这可能看起来像很多代码,但它说明了正确使用Scanner类所需的最少工作,并且不必处理困扰编程新手和这个名为java.util.Scanner可怕实现类的微妙错误和副作用. It tries to illustrate what idiomatic Java code should look like and behave like.它试图说明惯用的 Java 代码的外观和行为。

Below are some of the things I was thinking about when I wrote this example:下面是我在写这个例子时所考虑的一些事情:

JDK Version: JDK版本:

I purposely kept this example compatible with JDK 6. If some scenario really demands a feature of JDK 7/8 I or someone else will post a new answer with specifics about how to modify this for that version JDK.我特意让这个例子与 JDK 6 兼容。如果某些场景真的需要 JDK 7/8 的特性,我或其他人会发布一个新的答案,详细说明如何为那个版本的 JDK 修改它。

The majority of questions about this class come from students and they usually have restrictions on what they can use to solve a problem so I restricted this as much as I could to show how to do the common things without any other dependencies.关于这门课的大部分问题来自学生,他们通常对解决问题的方法有限制,所以我尽可能地限制了这一点,以展示如何在没有任何其他依赖的情况下做常见的事情。 In the 22+ years I have been working with Java and consulting the majority of that time I have never encountered professional use of this class in the 10's of millions of lines source code I have seen.在 22 年多的时间里,我一直在使用 Java 并在大部分时间进行咨询,我从未在我见过的数百万行源代码中遇到过此类的专业使用。

Processing commands:处理命令:

This shows exactly how to idiomatically read commands from the user interactively and dispatch those commands.这确切地显示了如何以交互方式惯用地从用户那里读取命令并分发这些命令。 The majority of questions about java.util.Scanner are of the how can I get my program to quit when I enter some specific input category.大多数关于java.util.Scanner的问题是关于当我输入某个特定的输入类别时如何让我的程序退出 This shows that clearly.这清楚地表明了这一点。

Naive Dispatcher天真的调度员

The dispatch logic is intentionally naive so as to not complicate the solution for new readers.调度逻辑是故意幼稚的,以免使新读者的解决方案复杂化。 A dispatcher based on a Strategy Pattern or Chain Of Responsibility pattern would be more appropriate for real world problems that would be much more complex.基于Strategy PatternChain Of Responsibility Strategy Pattern调度程序更适合于更复杂的现实世界问题。

Error Handling错误处理

The code was deliberately structured as to require no Exception handling because there is no scenario where some data might not be correct.代码被故意构建为不需要Exception处理,因为不存在某些数据可能不正确的情况。

.hasNext() and .hasNextXxx() .hasNext().hasNextXxx()

I rarely see anyone using the .hasNext() properly, by testing for the generic .hasNext() to control the event loop, and then using the if(.hasNextXxx()) idiom lets you decide how and what to proceed with your code without having to worry about asking for an int when none is available, thus no exception handling code.我很少看到有人正确使用.hasNext() ,通过测试通用.hasNext()来控制事件循环,然后使用if(.hasNextXxx())习惯用法让您决定如何以及如何处理您的代码无需担心在没有可用的情况下要求int ,因此没有异常处理代码。

.nextXXX() vs .nextLine() .nextXXX().nextLine()

This is something that breaks everyone's code.这会破坏每个人的代码。 It is a finicky detail that should not have to be dealt with and has a very obfusated bug that is hard to reason about because of it breaks the Principal of Least Astonishment这是一个不应该处理的挑剔细节,并且有一个非常模糊的错误,很难推理,因为它打破了 最小惊讶原则

The .nextXXX() methods do not consume the line ending. .nextXXX()方法不消耗行尾。 .nextLine() does. .nextLine()确实如此。

That means that calling .nextLine() immediately after .nextXXX() will just return the line ending.这意味着,调用.nextLine()后立即.nextXXX()将只返回结束行。 You have to call it again to actually get the next line.您必须再次调用它才能真正获得下一行。

This is why many people advocate either use nothing but the .nextXXX() methods or only .nextLine() but not both at the same time so that this finicky behavior does not trip you up.这就是为什么许多人主张要么只使用.nextXXX()方法,要么只使用.nextLine()但不要同时使用这两种方法,这样这种挑剔的行为就不会绊倒你。 Personally I think the type safe methods are much better than having to then test and parse and catch errors manually.我个人认为类型安全的方法比手动测试、解析和捕获错误要好得多。

Immutablity:不变性:

Notice that there are no mutable variables used in the code, this is important to learn how to do, it eliminates four of the most major sources of runtime errors and subtle bugs.请注意,代码中没有使用可变变量,这对于学习如何做很重要,它消除了运行时错误和细微错误的四个主要来源。

  1. No nulls means no possibility of a NullPointerExceptions !没有nulls意味着不可能出现NullPointerExceptions

  2. No mutability means that you don't have to worry about method arguments changing or anything else changing.无可变性意味着您不必担心方法参数更改或其他任何更改。 When you step debug through you never have to use watch to see what variables are change to what values, if they are changing.当您逐步调试时,您永远不必使用watch来查看哪些变量更改为哪些值(如果它们正在更改)。 This makes the logic 100% deterministic when you read it.这使得逻辑在您阅读时 100% 具有确定性。

  3. No mutability means your code is automatically thread-safe.无可变性意味着您的代码是自动线程安全的。

  4. No side effects.无副作用。 If nothing can change, the you don't have to worry about some subtle side effect of some edge case changing something unexpectedly!如果没有什么可以改变,你就不必担心某些边缘情况会意外改变某些东西的一些微妙的副作用!

Read this if you don't understand how to apply the final keyword in your own code. 如果您不了解如何在自己的代码中应用final关键字,请阅读本文。

Using a Set instead of massive switch or if/elseif blocks:使用 Set 而不是大量的switchif/elseif块:

Notice how I use a Set<String> and use .contains() to classify the commands instead of a massive switch or if/elseif monstrosity that would bloat your code and more importantly make maintenance a nightmare!请注意我如何使用Set<String>并使用.contains()来对命令进行分类,而不是使用大量switchif/elseif怪物,它们会膨胀您的代码,更重要的是使维护成为一场噩梦! Adding a new overloaded command is as simple as adding a new String to the array in the constructor.添加一个新的重载命令就像在构造函数的数组中添加一个新的String一样简单。

This also would work very well with i18n and i10n and the proper ResourceBundles .这也适用于i18ni10n以及适当的ResourceBundles A Map<Locale,Set<String>> would let you have multiple language support with very little overhead! Map<Locale,Set<String>>可以让您以很少的开销获得多语言支持!

@Nonnull @非空

I have decided that all my code should explicitly declare if something is @Nonnull or @Nullable .我决定我的所有代码都应该明确声明是@Nonnull还是@Nullable It lets your IDE help warn you about potential NullPointerException hazards and when you do not have to check.它让您的 IDE 帮助您警告潜在的NullPointerException危险以及何时不必检查。

Most importantly it documents the expectation for future readers that none of these method parameters should be null .最重要的是,它记录了未来读者的期望,即这些方法参数都不应该为null

Calling .close()调用 .close()

Really think about this one before you do it.在你做这件事之前真的想一想。

What do you think will happen System.in if you were to call sis.close() ?如果您调用sis.close()您认为System.in会发生什么? See the comments in the listing above.请参阅上面列表中的注释。

Please fork and send pull requests and I will update this question and answer for other basic usage scenarios.分叉并发送拉取请求,我将针对其他基本使用场景更新此问题和答案。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM