如何使用java.util.Scanner正确读取System.in中的用户输入并对其进行操作? [英] How to use java.util.Scanner to correctly read user input from System.in and act on it?

查看:112
本文介绍了如何使用java.util.Scanner正确读取System.in中的用户输入并对其进行操作?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述


这是一个规范问题/答案,可用作
重复目标。这些要求基于每天发布的最常见的
问题,并可根据需要添加。它们所有
都需要相同的基本代码结构来获得每个场景
并且它们通常彼此依赖。

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似乎是一个简单类,这就是第一个错误。它并不简单,它具有各种非明显的副作用和异常行为,以非常微妙的方式打破最小惊讶原则


Scanner seems like a "simple" class to use, and that is where the first mistake is made. 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.

所以这对于这个课程来说似乎有点矫枉过正,但洋葱错误和问题的剥离都是简单,但它们是一起的。非常复杂因为它们的相互作用和副作用。这就是为什么每天在Stack Overflow上有这么多关于它的问题。

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.

大多数扫描程序问题包括多次失败的尝试。

Most Scanner questions include failed attempts at more than one of these things.


  1. 我希望我的程序可以自动等待下一个也是在每次上一次输入之后输入的。

  1. I want to be able to have my program automatically wait for the next input after each previous input as well.

我想知道如何检测退出命令并在该命令时结束我的程序已输入。

I want to know how to detect an exit command and end my program when that command is entered.

我想知道如何以不区分大小写的方式匹配 exit 命令的多个命令。

I want to know how to match multiple commands for the exit command in a case-insensitive way.

我希望能够匹配正则表达式模式以及内置基元。例如,如何匹配看似日期的内容( 2014/10/18 )?

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

我想知道如何匹配可能无法通过正则表达式匹配实现的内容 - 例如,URL( http://google.com )。

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



动机:



在Java世界中,扫描仪是一个特例,它是一个非常挑剔的课程,教师不应该给新学生使用说明。在大多数情况下,教师甚至不知道如何正确使用它。它几乎不用于专业的生产代码,因此它对学生的价值非常值得怀疑。

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

使用扫描仪暗示这个问题和答案提到的所有其他事情。它绝不仅仅是关于扫描仪它是关于如何解决这些常见问题扫描仪这些问题始终是合并症几乎所有问题都是 Scanner 错误。它绝不仅仅是 next() vs nextLine() ,这只是该类实施的挑剔的症状,总有其他问题问题中的代码发布询问扫描程序

Using Scanner implies all the other things this question and answer mentions. 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. 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.

答案显示99%的案例完整,惯用的执行其中使用 Scanner 并询问StackOverflow。

The answer shows a complete, idiomatic implementation of 99% of cases where Scanner is used and asked about on 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 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.


您可能永远不会真正看到扫描仪用于
专业/商业业务应用程序,因为
所做的一切都是通过其他方式做得更好。真实世界的软件必须比$ code更具弹性和可维护性。扫描器允许你编写
代码。真实世界的软件使用标准化的文件格式解析器和
文档格式,而不是 adhoc 输入格式,在独立作业中给出

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. Real world software has to be more resilient and maintainable than Scanner allows you to write code. 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.


推荐答案

惯用例:



以下是如何正确使用 java.util.Scanner 类正确地从 System.in 以交互方式读取用户输入(有时称为 stdin ,特别是在C,C ++和其他语言以及Unix和Linux中)。它惯用地演示了要求完成的最常见的事情。

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


这可能看起来像很多代码,但它说明了使用扫描程序所需的最低
工作量正确的类,而不是
处理那些困扰那些新的
编程的微妙错误和副作用,以及这个非常实现的类
java.util.Scanner 。它试图说明惯用的Java代码
应该是什么样子和行为。

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. It tries to illustrate what idiomatic Java code should look like and behave like.

以下是一些我在编写这个例子时想到的事情:

我故意保持此示例与JDK 6兼容。如果某些场景确实需要JDK 7/8的功能,我或其他人将发布一个新的答案,其中包含有关如何为该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.

关于这门课程的大部分问题都来自学生,他们通常会限制他们可以用来解决问题的方法,因此我尽可能地限制这一点,以便展示如何在没有任何其他事情的情况下做常见的事情。依赖。 在22年多的时间里,我一直在使用Java并且大部分时间都在咨询我从未在我看过的数百万行源代码中遇到过这个类的专业用法。

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.

这显示了如何 idiomatically 以交互方式从用户读取命令并分派这些命令。关于 java.util.Scanner 的大多数问题属于当我输入某些特定输入类别时如何让我的程序退出。这清楚地表明了这一点。

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. This shows that clearly.

调度逻辑是故意天真的,以免使解决方案复杂化对于新读者。基于策略模式责任链模式的调度程序将更适合现实世界中的问题更复杂。

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.

代码是故意构造的,不需要异常处理,因为没有某些数据可能不正确的情况。

The code was deliberately structured as to require no Exception handling because there is no scenario where some data might not be correct.

我很少看到有人使用。 hasNext()正确地,通过测试通用 .hasNext()来控制事件循环,然后使用 if(.hasNextXxx()) idiom让你决定如何以及如何处理你的代码,而不必担心在没有的情况下要求 int 是可用的,因此没有异常处理代码。

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.

这可以打破每个人的代码。这是一个挑剔的细节,不应该被处理并且有一个非常难以理解的错误,因为它打破了最小惊讶的主要原因

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

.nextXXX()方法不消耗行尾。 .nextLine()确实。

The .nextXXX() methods do not consume the line ending. .nextLine() does.

这意味着调用 .nextLine() .nextXXX()之后立即返回结束行。你必须再次调用才能真正获得下一行。

That means that calling .nextLine() immediately after .nextXXX() will just return the line ending. You have to call it again to actually get the next line.

这就是为什么很多人提倡使用 .nextXXX()方法或只有 .nextLine()但不能同时进行这两种操作,以便这种挑剔的行为不会让你失望。我个人认为类型安全方法要比手动测试和解析并捕获错误要好得多。

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. Personally I think the type safe methods are much better than having to then test and parse and catch errors manually.

请注意,代码中没有使用可变变量,这对于学习如何操作很重要,它消除了四个最主要的运行时错误和微妙的错误来源。

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. nulls 表示不可能有 NullPointerExceptions

不可变性意味着您不必担心方法参数更改或其他任何更改。当您通过调试步骤时,您永远不必使用 watch 来查看哪些变量会更改为哪些值(如果它们正在更改)。这使得逻辑在读取时具有100%的确定性。

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. This makes the logic 100% deterministic when you read it.

不可变性意味着您的代码自动是线程安全的。

No mutability means your code is automatically thread-safe.

无副作用。如果没有什么可以改变的话,你不必担心某些边缘情况出现意外改变的微妙副作用!

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!

如果您不明白,请阅读此内容如何在您自己的代码中应用 final 关键字。

注意我是怎么做的使用 Set< String> 并使用 .contains()对命令进行分类,而不是大量切换如果/ elseif 怪物会破坏你的代码,更重要的是让维护成为一场噩梦!添加一个新的重载命令就像在构造函数中向数组中添加一个新的 String 一样简单。

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! Adding a new overloaded command is as simple as adding a new String to the array in the constructor.

这也是与 i18n i10n 以及正确的 ResourceBundles 。
A Map< Locale,Set< String>> 可以让您获得多种语言支持而且开销很小!

This also would work very well with i18n and i10n and the proper ResourceBundles. A Map<Locale,Set<String>> would let you have multiple language support with very little overhead!

我已经确定我的所有代码都应显式声明某些东西是 @Nonnull @Nullable 。它允许您的IDE帮助警告您潜在的 NullPointerException 危险以及何时无需检查。

I have decided that all my code should explicitly declare if something is @Nonnull or @Nullable. It lets your IDE help warn you about potential NullPointerException hazards and when you do not have to check.

最重要的是它记录了未来读者的期望,这些方法参数都不应该 null

Most importantly it documents the expectation for future readers that none of these method parameters should be null.

在你这样做之前真的想想这个。

Really think about this one before you do it.

你认为会发生什么 System.in 如果你打电话给 sis.close()?请参阅上面列表中的评论。

What do you think will happen System.in if you were to call sis.close()? See the comments in the listing above.

这篇关于如何使用java.util.Scanner正确读取System.in中的用户输入并对其进行操作?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆