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

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

问题描述

<块引用>

这是一个规范的问题/答案,可以用作重复目标.这些要求基于最常见的每天发布的问题,可以根据需要添加.他们都需要相同的基本代码结构才能到达每个场景并且他们通常相互依赖.

<小时>

Scanner 似乎是一个简单" 使用的类,这就是犯第一个错误的地方.它并不简单,它具有各种不明显的副作用和异常行为,以非常微妙的方式打破了最小惊讶原则.

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

常见的扫描仪问题:

大多数 Scanner 问题都包括尝试不止一项这些事情的失败.

  1. 我希望能够让我的程序在每次输入后自动等待下一个输入.

  2. 我想知道如何检测exit命令并在输入该命令时结束我的程序.

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

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

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

动机:

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

使用 Scanner 暗示了这个问题和答案提到的所有其他事情.它绝不仅仅是关于Scanner,而是关于如何使用Scanner解决这些常见问题,这些问题在几乎所有获得Scanner的问题中都是共病问题代码> 错误.它绝不仅仅是关于 next() vs nextLine(),这只是类实现的挑剔的一个症状,代码中总是有其他问题在询问Scanner.

答案显示了 99% 使用 Scanner 并在 StackOverflow 上被询问的情况的完整、惯用的实现.

尤其是在初学者代码中.如果您认为这个答案太复杂,那么在解释其行为的复杂性、怪癖、不明显的副作用和特殊性之前,请向告诉新学生使用 Scanner 的教师投诉.

Scanner 是关于最小惊讶原则的重要性的绝佳教学时刻 是以及为什么一致的行为和语义在命名方法和方法参数时很重要.

学生注意事项:

<块引用>

您可能永远不会真正看到 Scanner 用于专业/商业业务应用程序,因为它的一切做其他事情做得更好.现实世界的软件必须是比 Scanner 更具弹性和可维护性,允许您编写代码.现实世界的软件使用标准化的文件格式解析器和记录的文件格式,而不是您所使用的adhoc 输入格式在独立作业中给出.

解决方案

惯用示例:

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

package com.stackoverflow.scanner;导入 javax.annotation.Nonnull;导入 java.math.BigInteger;导入 java.net.MalformedURLException;导入 java.net.URL;导入 java.util.*;导入 java.util.regex.Pattern;导入静态 java.lang.String.format;公共类 ScannerExample{private static final Set退出命令;private static final Set帮助命令;私有静态最终模式 DATE_PATTERN;私有静态最终字符串 HELP_MESSAGE;静止的{最终 SortedSetecmds = new TreeSet(String.CASE_INSENSITIVE_ORDER);ecmds.addAll(Arrays.asList("exit", "done", "quit", "end", "fino"));EXIT_COMMANDS = Collections.unmodifiableSortedSet(ecmds);最终 SortedSethcmds = new TreeSet(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/1HELP_MESSAGE = format("请输入一些数据或输入以下命令之一退出%s", EXIT_COMMANDS);}/*** 使用异常来控制执行流程总是不好的.* 这就是为什么 this 被封装在一个方法中,这是这样做的* 方式特别是为了不引入任何外部库* 所以这是一个完全独立的例子.* @param 可能的 url* @return 如果 s 代表一个有效的 url,则为真,否则为假*/private static boolean isValidURL(@Nonnull final String s){尝试 { 新 URL(s);返回真;}catch (final MalformedURLException e) { return false;}}私有静态无效输出(@Nonnull final String 格式,@Nonnull final Object... args){System.out.println(format(format, args));}public static void main(final String[] args){最终扫描仪 sis = 新扫描仪(System.in);输出(HELP_MESSAGE);而 (sis.hasNext()){如果(sis.hasNextInt()){最终 int next = sis.nextInt();output("您输入了一个整数 = %d", next);}否则如果(sis.hasNextLong()){最后长下一个 = sis.nextLong();output("你输入了一个 Long = %d", next);}否则如果(sis.hasNextDouble()){最后双下一个 = sis.nextDouble();output("你输入了一个 Double = %f", next);}否则如果 (sis.hasNext("\d+")){最终 BigInteger next = sis.nextBigInteger();output("你输入了一个 BigInteger = %s", next);}否则如果(sis.hasNextBoolean()){最终布尔下一个 = sis.nextBoolean();output("您输入了一个布尔表示 = %s", next);}否则如果(sis.hasNext(DATE_PATTERN)){最终字符串下一个 = sis.next(DATE_PATTERN);output("你输入了一个日期表示 = %s", next);}else//未分类{最终字符串下一个 = sis.next();如果(isValidURL(下一个)){output("您输入了一个有效的 URL = %s", next);}别的{如果 (EXIT_COMMANDS.contains(next)){output("退出命令 %s 发出,退出中!", next);休息;}否则 if (HELP_COMMANDS.contains(next)) { output(HELP_MESSAGE);}else { output("你输入了一个未分类的字符串 = %s", next);}}}}/*这将关闭底层 InputStream,在本例中为 System.in,并释放这些资源.警告:调用 .close() 后,您将无法再从 System.in 中读取数据.如果您想将 System.in 用于其他用途,请不要关闭 Scanner.*/sis.close();System.exit(0);}}

注意事项:

<块引用>

这可能看起来像很多代码,但它说明了最少的正确使用 Scanner 类所需的努力,而不必处理困扰新手的微妙错误和副作用编程和这个可怕的实现类称为java.util.Scanner.它试图说明什么是惯用的 Java 代码应该看起来和行为相似.

下面是我写这个例子时的一些想法:

JDK 版本:

我特意让这个例子与 JDK 6 兼容.如果某些场景真的需要 JDK 7/8 的特性,我或其他人会发布一个新的答案,详细说明如何为那个版本的 JDK 修改它.

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

处理命令:

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

朴素的调度员

调度逻辑是有意为之的,以免新读者的解决方案复杂化.基于 Strategy PatternChain Of Responsibility 模式的调度程序更适合处理更复杂的现实世界问题.

错误处理

代码经过精心设计,不需要Exception处理,因为不存在某些数据可能不正确的情况.

.hasNext().hasNextXxx()

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

.nextXXX() vs .nextLine()

这会破坏每个人的代码.这是一个挑剔的细节,不应该被处理并有一个非常模糊的错误,很难推理,因为它打破了最低限度的原则

.nextXXX() 方法不消耗行尾..nextLine() 可以.

这意味着在 .nextXXX() 之后立即调用 .nextLine() 只会返回行尾.你必须再次调用它才能真正得到下一行.

这就是为什么很多人提倡只使用 .nextXXX() 方法或只使用 .nextLine() 但不要同时使用这两种方法的原因行为不会绊倒你.我个人认为类型安全的方法比必须手动测试、解析和捕获错误要好得多.

不变性:

请注意,代码中没有使用可变变量,了解如何操作很重要,它消除了运行时错误和细微错误的四个主要来源.

  1. 没有 nulls 意味着不可能出现 NullPointerExceptions

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

  3. 无可变性意味着您的代码是自动线程安全的.

  4. 没有副作用.如果什么都不能改变,你就不必担心某些边缘情况会意外改变某些东西的一些微妙的副作用!

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

使用 Set 而不是大量的 switchif/elseif 块:

注意我如何使用 Set 并使用 .contains() 来对命令进行分类,而不是使用大量的 switchif/elseif 怪物会膨胀你的代码,更重要的是让维护成为一场噩梦!添加一个新的重载命令就像在构造函数的数组中添加一个新的 String 一样简单.

这也适用于 i18ni10n 以及适当的 ResourceBundles.Map> 可以让您以很少的开销获得多语言支持!

@Nonnull

我决定我的所有代码都应该明确声明是否有@Nonnull@Nullable.它可以让您的 IDE 帮助您警告潜在的 NullPointerException 危险以及何时无需检查.

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

调用 .close()

在做这件事之前要好好想想.

如果您调用 sis.close(),您认为 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 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.

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.

Common Scanner questions:

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

  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.

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

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

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.

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.

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.

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

解决方案

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:

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.

Below are some of the things I was thinking about when I wrote this example:

JDK Version:

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.

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.

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

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.

.hasNext() and .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.

.nextXXX() vs .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. .nextLine() does.

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.

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.

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!

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

  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.

Using a Set instead of massive switch or if/elseif blocks:

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.

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

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.

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

Calling .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()? 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.

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

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